es6 尾递归

什么是尾递归?
函数调用自身,称为递归。如果尾部调用自身,就称为尾递归。
递归本来非常耗内存,因为需要同时保存成千上百个调用帧,很容易发生"栈溢出"错误。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生"栈溢出"错误。

function factorial(n){
	if(n === 1) return 1;
	return n * factorial( n -1 );
}
factorial(5) // 120 
//上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度O(n)。
//如果改成尾递归,只保留一个调用记录,复杂度O(1)。
function factorial(n,total){
	if(n === 1) return total;
	return factorial(n -1, n * total);
}
factirial(5,1) //120

还有一个比较著名的例子,就是计算Fibonacci数列,也能充分说明尾递归优化的重要性。
非尾递归的Fibonacci数列实现如下。

function Fibonacci(n){
	if(n <= 1){return 1};
	return Fibonacci(n -1)+Fibonacci(n-2);
}
Fibonacci(10) //89
Fibonacci(100)//超时
Fibonacci(500)//超时

尾递归优化过的Fibonacci数列实现如下。

function Fibonacci2(n,ac1 = 1,ac2 =1){
	if( n<=1){return ac2};
	return Fibonacci2(n -1 ,ac2,ac1+ac2);
}
Fibonacci2(100) 		 //573147844013817200000
Fibonacci2(1000)	//7.0330367711422765e+208
Fibonacci2(10000)	//Infinity
//由此可见,"尾调用优化"对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6亦是如此,第一次明确规定,所有EMAScript的实现,都必须部署"尾部调用优化"。这就是说,ES6中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。

递归函数的改写
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数factorial需要用到一个中间变量total,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1?
两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。

function tailFactorial(n,total){
	if(n === 1) return total;
	return tailFactorial(n -1,n * total);
}
function factorial(n){
	return tailFactorial(n,1);
}
factorial(5)  // 120

上面代码通过一个正常形式的阶乘函数factorial,调用尾递归函数tailFactorial,看起来就正常多了。
函数是变成有一个概念,叫做柯里化(currying),意思试讲多参数的函数转化成单参数的形式。这里也可以使用柯里化。

function currying(fn,n){
	return function (m){
		return fn.call(this,m,n);
	}
}
fucntion tailFactorial(n,total){
	if(n === 1) return total;
	return railFactorial(n -1, n * total);
}
const factorial = currying(tailFactorial,1);
factorial(5) // 120

上面代码通过柯里化,将尾递归函数tailFactorial变为只接受一个参数的factorial。
第二种方法就简单多了,就是采用ES6的函数默认和值。

fucntion factorial(n,total = 1){
	if(n ===1) return total;
	return factorial(n -1, n*total);
}
factorial(5)  // 120

上面代码中,参数total有默认值1,所以调用时不同提供这个值。
总结
递归本质上是一种循环操作。纯粹的函数式编程语言没有训话操作命令,所有的循环都用递归实现,这就是为什么尾递归对着写语言及其重要。对于其他支持"尾调用优化"的语言(比如lua,ES6),只需要知道循环可以用递归代替,而一但是用递归,就最好使用尾递归。

声明:这片文章也是参考大佬阮一峰前辈
ES6 阮一峰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值