尾调用+尾调用优化

什么是尾调用

尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。例如:

function f(x) {
	return g(x);
}

上面的代码中,函数f的最后一步是调用函数g,这就是尾调用。

注意:1.尾调用是指函数的最后一步是调用另一个函数,如果调用另一个函数之后还有其他操作,即使语义完全一样也不属于尾调用。
2.尾调用不一定出现在函数尾部,只要是最后一步操作即可。例如:

function f(x) {
	if (x > 0) {
		return m(x);
	}
	return n(x);
}

上面的代码中,函数m和n都属于尾调用,因为他们都是函数f的最后一步操作。

尾调用优化

尾调用之所以与其他调用不同,就在于其特殊的调用位置。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、每部变量等信息都不会再用到了,直接用内层函数的调用帧取代外层函数即可。

function f() {
	let m = 1;
	let n = 2;
	return g(m + n);
}
f();

等同于

function f() {
	return g(3);
}
f();

等同于

g(3);

上面的代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。

  • 这就是“尾调用优化”(Tail Call Optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做大每次执行是调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

    注意:只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

    补充:我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”,保存调用位置和内部变量等信息。所有的调用帧就会形成一个“调用栈”

尾递归

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

function factorial(n) {
	if (n === 1) {
		return 1;
	}
	return n * factorial(n - 1);			
}
console.log(factorial(5)); //120

上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度为O(n)。
如果改写为尾递归,只保留一个调用记录,则复杂度为O(1)。

function factorial(n, sum) {
	if (n === 1) {
		return sum;
	}
	return factorial(n - 1,n * sum);			
}
console.log(factorial(5,1)); //120

下面再举一个例子,计算斐波那契数列,也能充分说明尾递归优化的重要性。
非尾递归的斐波那契数列实现入下:

function fibonacci(n) {
	if (n <= 1) {
		return 1;
	}
	return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(10)); //89
console.log(fibonacci(100)); //堆栈溢出
console.log(fibonacci(500)); //堆栈溢出

尾递归优化的斐波那契数列实现:

function fibonacci(n, ac1 = 1, ac2 = 1) {
	if (n <= 1) {
		return ac2;
	}
	return fibonacci(n - 1, ac2, ac1 + ac2);
}
console.log(fibonacci(10)); //89
console.log(fibonacci(100)); //573147844013817200000
console.log(fibonacci(500)); //2.2559151616193602e+104

由此可见。“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有ECMAScrit的实现都必须部署“尾调用优化”。这就是说,在ES6中,只要使用尾递归,就不会发货所能栈溢出,相对节省空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值