在 ECMAScript 2015 语言规范中, 和 的定义Function.prototype.apply
都Function.prototype.call
将“Perform PrepareForTailCall()”作为其步骤之一,因此我们知道这些函数支持正确的尾调用(即尾调用优化)。
但是,绑定函数对象上的 [[Call]]的定义省略了 PrepareForTailCall()。这是否意味着绑定函数不支持正确的尾调用,并且递归调用自身的绑定函数可能会炸毁堆栈?
在 ECMAScript 2015 语言规范中, 和 的定义Function.prototype.apply
都Function.prototype.call
将“Perform PrepareForTailCall()”作为其步骤之一,因此我们知道这些函数支持正确的尾调用(即尾调用优化)。
但是,绑定函数对象上的 [[Call]]的定义省略了 PrepareForTailCall()。这是否意味着绑定函数不支持正确的尾调用,并且递归调用自身的绑定函数可能会炸毁堆栈?
[[Call]]
绑定函数对象的定义省略了PrepareForTailCall()
. 这是否意味着绑定函数不支持正确的尾调用,并且递归调用自身的绑定函数可能会炸毁堆栈?
不。在调用表达式的评估过程中PrepareForTailCall
发生,它检查该表达式是否位于尾部位置。当尾调用准备好时,当前运行的执行上下文被丢弃,在函数被调用之前,在各自的内部方法上调度。新的运行执行上下文是从用户定义函数的方法中设置的。绑定函数的方法只是在此之前引入了额外的间接级别。EvaluateDirectCall
[[Call]]
PrepareForOrdinaryCall
[[Call]]
[[Call]]
在 ECMAScript 2015 语言规范中, 和 的定义
Function.prototype.apply
都Function.prototype.call
将“执行PrepareForTailCall()
”作为其步骤之一,因此我们知道这些函数支持正确的尾调用。
是的,这是必要的,因为[[Call]]
内置函数的方法设置了一个新的运行执行上下文(用于“实现定义的步骤”)。正是这个“内置上下文”PrepareForTailCall
将在调用实际函数之前丢弃。
call
和方法是调用函数的apply
函数,当它们被调用时,堆栈上有两个调用需要进行尾调用优化。(对比一下 - 例如 - Array.prototype.map
,它也调用其他函数,但这里的map
执行上下文保留在调用堆栈上。在call
和apply
中,Call()
确实在算法的尾部位置)。
请参阅 §12.3.4.1(运行时语义:评估)。首先,我们做IsInTailPosition
,然后我们做EvaluateDirectCall
——结果是F.[[Call]]
。换句话说,当我们进行实际调用时,我们已经知道我们是否处于尾部位置。
但是,正如您所提到的,除了 Safari ,JS在任何浏览器上都没有进行尾部优化。