JS递归调用栈溢出

1,732 阅读2分钟

函数递归容易导致调用栈溢出的问题,学习和实践一下如何处理
整体思路是把递归转换成循环执行,这样函数执行完一次后会被弹出调用栈

以计算阶乘的递归为例子。

  • 初始的方法
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));

参考: www.jianshu.com/p/077e52d60…