JavaScript 的早期版本不允许命名函数表达式,因此我们无法创建递归函数表达式:
// This snippet will work:
function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
}
[1,2,3,4,5].map(factorial);
// But this snippet will not:
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : /* what goes here? */ (n-1)*n;
});
为了解决这个问题,arguments.callee
添加后我们可以这样做:
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : arguments.callee(n-1)*n;
});
然而,这实际上是一个非常糟糕的解决方案,因为这(与其他参数、被调用者和调用者问题结合起来)使得内联和尾递归在一般情况下变得不可能(您可以通过跟踪等在选择的情况下实现它,但即使是最好的代码由于检查是不必要的,因此不是最佳的)。另一个主要问题是递归调用会得到不同的结果this
值,例如:
var global = this;
var sillyFunction = function (recursed) {
if (!recursed)
return arguments.callee(true);
if (this !== global)
alert("This is: " + this);
else
alert("This is the global");
}
sillyFunction();
无论如何,EcmaScript 3 通过允许命名函数表达式解决了这些问题,例如:
[1,2,3,4,5].map(function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
});
这有很多好处:
Whoops,
刚刚意识到除了其他一切之外,问题还在于arguments.callee.caller
,或更具体地说Function.caller https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller.
在任何时间点,您都可以找到堆栈上任何函数的最深调用者,正如我上面所说,查看调用堆栈有一个主要影响:它使大量优化变得不可能,或者变得更加困难。
例如。如果我们不能保证一个函数f
不会调用未知函数,那么就不可能内联f
。基本上,这意味着任何可能微不足道的内联调用站点都会积累大量的守卫,取:
function f(a, b, c, d, e) { return a ? b * c : d * e; }
如果 js 解释器不能保证所有提供的参数在调用时都是数字,则需要在内联代码之前插入对所有参数的检查,否则无法内联该函数。
现在,在这种特殊情况下,智能解释器应该能够重新安排检查以使其更加优化,并且不检查任何不会使用的值。然而,在许多情况下这是不可能的,因此内联变得不可能。