为什么JavaScript中不推荐使用arguments.callee.caller
属性?
它在JavaScript中被添加,然后被弃用,但ECMAScript完全忽略了它.一些浏览器(Mozilla,IE)一直支持它,并且没有任何取消支持的计划.其他浏览器(Safari、Opera)也采用了对它的支持,但在旧浏览器上的支持是不可靠的.
有没有充分的理由将这一有价值的功能搁置一旁?
(或者,有没有更好的方法抓住调用函数的句柄?)
为什么JavaScript中不推荐使用arguments.callee.caller
属性?
它在JavaScript中被添加,然后被弃用,但ECMAScript完全忽略了它.一些浏览器(Mozilla,IE)一直支持它,并且没有任何取消支持的计划.其他浏览器(Safari、Opera)也采用了对它的支持,但在旧浏览器上的支持是不可靠的.
有没有充分的理由将这一有价值的功能搁置一旁?
(或者,有没有更好的方法抓住调用函数的句柄?)
早期版本的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;
});
这有很多好处:
可以从代码内部像调用任何其他函数一样调用该函数.
它不会污染名称空间.
this
的值不变.
它的性能更高(访问arguments object是昂贵的).
我刚刚意识到,除了其他所有问题之外,这个问题是关于arguments.callee.caller
个,或者更确切地说是Function.caller
个.
在任何时候,你都可以找到堆栈上任何函数最深层的调用方,正如我前面所说的,查看调用堆栈有一个主要影响:它使大量优化变得不可能,或者更加困难.
例如.如果我们不能保证函数f
不会调用未知函数,那么就不可能内联f
.基本上,这意味着任何可能是微不足道的可内联的调用点都会积累大量的警卫,采取:
function f(a, b, c, d, e) { return a ? b * c : d * e; }
如果js解释器不能保证在调用时提供的所有参数都是数字,那么它需要在内联代码之前插入所有参数的判断,或者不能内联函数.
现在,在这种特殊情况下,智能解释器应该能够重新安排判断,使其更加优化,并且不会判断任何不会使用的值.然而,在许多情况下,这是不可能的,因此无法内联.