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)
答案 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
的指令。然后,我们执行它们……依此类推,直到最终得到一个非函数返回值。
如评论中所述,一些浏览器已经实现了尾部呼叫优化,这使得蹦床变得不必要(但仍然值得了解)。