解决递归调用栈溢出问题
ES6允许函数内部使用严格模式,而尾调用必须在严格模式下才能使用,因此函数必须使用‘use strict’,尾递归优化只在严格模式下生效,那么正常模式下,或者那些不支持严格模式的环境中,有没有办法也使用尾递归优化呢?
1.什么是尾递归?
尾调用是指函数的最后一步是调用另一个函数。那么尾递归就是最后一步是调用自己。我们知道,在函数调用时,会产生一个调用栈,执行内部函数时会把这个函数push到栈首,内部函数执行完会将状态、数据返回给栈中的下一层,并pop出栈。如果使用尾调用,你会发现,栈中不需要一个个存栈,因为我只需要最后执行的那个函数的数据。因此,对于一些复杂度为O(n)的函数,可以优化到O(1)。
2.非严格模式下的尾递归优化
思路:过多的递归层数会是的函数调用栈溢出,那么我们是不是可以把 ‘递归’ 转换为 ‘循环’
2.1 蹦床函数
我们可以写一个函数,传一个函数进去。此函数就是我们的递归函数,但是我们需要处理一下:边界的地方不进行处理,我们对自身函数的执行更改为返回一个函数(一个新函数)。比如对1,1000000求和:
//改进后的函数
function sum(x, y) {
if (y > 0) {
return sum.bind(null, x + 1, y - 1); //bind返回了一个新函数,正常递归是执行sum函数,如果用递归,调用栈会满。
} else {
return x;
}
}
//蹦床函数
function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}
trampoline(sum(1,100000));
//把sum函数传入,执行循环:满足条件后执行sum函数,返回一个新函数。
2.2真正的尾递归优化
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) {
return sum(x + 1, y - 1)
}
else {
return x
}
});
sum(1, 100000)
3.结尾
对于实现尾递归,重点在于将’递归’转换为’循环’。代码、思路来源:阮一峰《ECMAScript 6 入门》
有兴趣的猿可以深入看看。