这是阮一峰大神的《ES6标准入门》一书中写道的,没看过这节的童鞋可以狠戳这里。
当时我就很不理解:除了最后一次调用不是尾递归,其余 y 大于 0 的时候,都是尾递归;那么,按照书“尾调用优化”上所说的,尾递归本身已经是“尾调用优化”过了,为什么还没报Uncaught RangeError: Maximum call stack size exceeded(…)——调用栈溢出这种错误?
于是,“我曾经问遍整个世界”,然而并没有“从来没得到答案”,这里我要感谢我的胡桃夹子给了我灵感��!至于谁是胡桃夹子,嘿嘿,我不告诉你,相信你以后会知道他的!
好了,废话不多说,开始分析吧。。
首先,先来了解一下什么叫栈溢出:
“同一时刻只能有一个函数在运行”主要是由于JS是单线程的。
另外,值得提醒的是firstFn
并不是尾调用!因为,这个函数最后执行的是return undefined
,只有一个函数内最后执行的语句是return xxx()
才算尾调用,因此firstFn
的栈对象,在secondFn
执行时,并没有被释放掉。
其实,图一里面的这个函数是尾递归的,但是很坑爹的是浏览器没有实现尾递归,产生省内存的效果!
你不信,你可以复制下面这段代码到你的编辑器,打开Chrome浏览器的Sources看看调用栈中每次debugger
的变化。
function sum(x, y) {
if (y > 0) {
debugger;
return sum(x + 1, y - 1);
} else {
return x;
}
}
sum(1, 100);
你会发现了每次debugger
都会产生添加新的调用栈信息,是不是很神奇?!按正常来说,我尾递归了,就应该省内存!但没有,事实上是浏览器发现尾递归之后,没有进行尾递归优化!
于是,在阮大神的书里面附上了本应该浏览器内置的“尾递归优化的实现”,其思路是递归执行转为循环执行。
再次,复制下面这段代码到你的编辑器,打开Chrome浏览器的Sources看看调用栈中每次debugger
的变化。
function tco(f) {
var value;
var active = false;
var accumulated = [];
return function accumulator() {
accumulated.push(arguments);
if (!active) {
active = true;
while (accumulated.length) {
value = f.apply(this, accumulated.shift());
}
active = false;
return value;
}
};
}
var sum = tco(function(x, y) {
if (y > 0) {
debugger;
return sum(x + 1, y - 1)
}
else {
return x
}
});
sum(1, 100000)
这次就可以看到Sources的调用栈在每次debugger
后,只保留了一条sum信息!
好了,解释完毕!其实,本片文章只是阮大神那本书那节的一个详细解读啦~