蹦床功能操作原理

时间:2019-03-23 10:07:29

标签: javascript

function trampoline(f) {
  while (f && f instanceof Function) {
    f = f();
  }
  return f;
}
function sum(x, y) {
  if (y > 0) {
  //   return sum(x + 1, y - 1);  Maximum call stack size exceeded
  return sum.bind(null, x + 1, y - 1); //100001
  } else {
    return x;
  }
}
sum(1, 100000) //Maximum call stack size exceeded
let value= trampoline(sum(1, 100000))
console.log(value)

  1. 为什么使用蹦床功能而不会导致堆栈溢出
  2. 为什么蹦床函数中的sum函数需要绑定

2 个答案:

答案 0 :(得分:1)

  

1。为什么使用蹦床功能而不会导致堆栈溢出

function自身调用超过限制时,将抛出最大调用堆栈。在trampoline中有一个while循环而不是递归的,所以它工作正常。

  

2。为什么蹦床函数中的sum函数需要绑定

您可以在不使用bind()的情况下进行操作。使用绑定是因为它返回一个新的function。如果您使用包装器功能,它仍然可以正常工作

function trampoline(f) {
  while (f && f instanceof Function) {
    f = f();
  }
  return f;
}
function sum(x, y) {
  if (y > 0) {
  //   return sum(x + 1, y - 1);  Maximum call stack size exceeded
  return () => sum(x + 1, y - 1); 
  } else {
    return x;
  }
}
sum(1, 100000) //Maximum call stack size exceeded
let value= trampoline(sum(1, 100000))
console.log(value)

您可能会问为什么我们需要使用包装器功能。因为如果我们不使用包装器功能,而只是做

return sum(x + 1, y - 1);

这将导致递归。当我们使用包装函数或bind()时,会返回一个新的function,但不会调用它,因此会发生递归并引发错误。

答案 1 :(得分:1)

让我们简化一下示例:

function sum_tramp(x, y) {
    if (y > 0)
        return () => sum_tramp(x + 1, y - 1);
    else
        return x;
}

function sum_rec(x, y) {
    if (y > 0)
        return sum_rec(x + 1, y - 1);
    else
        return x;
}

x = sum_tramp(1, 1e6)
while (x instanceof Function)
    x = x();
console.log(x)


x = sum_rec(1, 1e6) //Maximum call stack size exceeded
console.log(x)

天真递归(sum_rec)需要先计算所有值,然后才能返回任何内容,因此要计算6+4,就必须计算7+3,这需要8+2等该函数直到所有后续调用完成并且其参数一直位于堆栈上才退出。

蹦床返回计算值,即指示如何计算值而不是值本身的指令。由于计算是已知的,因此该函数将立即退出并且不会填充堆栈。在主程序中,我们检查返回的值是否为计算值,如果是,则只需再次调用它即可。因此,在计算6+4时,我们会收到有关如何计算7+3的说明。我们执行这些指令,并获得有关如何计算8+2的指令。然后,我们执行它们……依此类推,直到最终得到一个非函数返回值。

如评论中所述,一些浏览器已经实现了尾部呼叫优化,这使得蹦床变得不必要(但仍然值得了解)。