函数递归容易导致调用栈溢出的问题,学习和实践一下如何处理
整体思路是把递归转换成循环执行,这样函数执行完一次后会被弹出调用栈
以计算阶乘的递归为例子。
- 初始的方法
function factorial (n, res) {
if (n === 0) {
return res
}
res = n * res
return factorial(n - 1, res)
}
const the100fac = factorial(11396, 1);
console.log(the100fac);
我的机器计算11396就会栈溢出
1.通过setTimeout来处理:
function factorial (n, res) {
if (n === 0) {
console.log(res)
return res
}
res = n * res
return ((res) => {
// console.log(res)
setTimeout(function() {
factorial(n - 1, res)
}, 0)
})(res);
// return factorial(n - 1, res)
}
const the100fac = factorial(11396, 1);
console.log(the100fac);
思路就是不把递归里的函数放到调用栈里,比如通过setTimeout来控制。
但是这样的就会有作用域及this指针的问题,需要修改一些业务逻辑。
而且调用有一个最小的时间间隔,又是异步的,即时性也不好。
2.通过promise来处理:
function factorial (n, res) {
if (n === 0) {
return Promise.resolve()
.then(() => {
return res;
})
}
res = n * res
return Promise.resolve()
.then(() => {
return factorial(n - 1, res)
})
// return new Promise.resolve().then(factorial(n - 1, res))
}
const the100fac = factorial(11396, 1);
console.log(the100fac.then((res) => { console.log(res) }))
使用promise把递归放到微任务里执行
这个还能接受,微任务即时性挺好
看书发现了尾递归优化
ES6的新特性,然而发现宿主环境支持的不好(safari支持)。
3.蹦床函数
之前的函数内部返回一个函数,通过bind返回,不直接执行,交给蹦床函数执行。
function factorial (n, res) {
if (n === 0) {
return res
}
res = n * res
return factorial.bind(null, n - 1, res)
}
function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}
const the100fac = trampoline(factorial(11396, 1));
console.log(the100fac);
4.尾递归优化的实现
依旧是通过循环来替代递归
function tco(f) {
var value;
var active = false;
var accumulated = [];
return function accumulator() {
accumulated.push(arguments);//每次将参数传入. 例如, 1 100000
if (!active) {
active = true;
while (accumulated.length) {//出循环条件, 当最后一次返回一个数字而不是一个函数时, accmulated已经被shift(), 所以出循环
value = f.apply(this, accumulated.shift());//调用累加函数, 传入每次更改后的参数, 并执行
}
active = false;
return value;
}
};
}
const the100fac = tco(function(n, res) {
if (n === 0) {
return res
}
res = n * res
return the100fac(n - 1, res)
});
console.log(the100fac(11396, 1));