为什么在JavaScript中不推荐使用arguments.callee.caller属性?

时间:2008-09-19 16:45:38

标签: javascript ecma262

为什么JavaScript中不推荐使用arguments.callee.caller属性?

它已添加,然后在JavaScript中弃用,但ECMAScript完全省略了它。某些浏览器(Mozilla,IE)一直支持它,并且在地图上没有任何计划来删除支持。其他人(Safari,Opera)已经采用了支持,但对旧浏览器的支持是不可靠的。

是否有充分的理由将这些有价值的功能置于不确定状态?

(或者,有没有更好的方法来获取调用函数的句柄?)

5 个答案:

答案 0 :(得分:247)

早期版本的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

在任何时候你都可以找到堆栈上任何函数的最深调用者,正如我上面所说,查看调用堆栈有一个主要的影响:它使大量的优化成为不可能,或者说很多更难。

EG。如果我们不能保证函数f不会调用未知函数,则无法内联f。基本上这意味着任何可能已经无法解决的呼叫站点都会积累大量的警卫,采取:

 function f(a, b, c, d, e) { return a ? b * c : d * e; }

如果js解释器不能保证所有提供的参数都是调用时的数字,则需要在内联代码之前插入对所有参数的检查,否则它不能内联函数。

现在在这种特殊情况下,智能解释器应该能够重新安排检查以使其更加优化,并且不会检查任何不会使用的值。然而,在许多情况下,这是不可能的,因此无法内联。

答案 1 :(得分:89)

arguments.callee.caller 已弃用,但它确实使用了Function.caller属性。 (arguments.callee只会为您提供当前函数的参考)

  • Function.caller,虽然根据ECMA3是非标准的,但是在所有当前主流浏览器中实施。
  • arguments.caller 已弃用Function.caller,并未在某些当前主流浏览器(例如Firefox 3)中实施。

所以情况不太理想,但如果你想在所有主流浏览器中使用Javascript访问调用函数,你可以使用Function.caller属性,可以直接在命名函数引用上访问,也可以从内部访问通过arguments.callee属性的匿名函数。

答案 2 :(得分:29)

最好使用命名函数而不是arguments.callee:

 function foo () {
     ... foo() ...
 }

优于

 function () {
     ... arguments.callee() ...
 }

指定的函数可以通过 caller 属性访问其调用者:

 function foo () {
     alert(foo.caller);
 }

优于

 function foo () {
     alert(arguments.callee.caller);
 }

弃用是由于当前的ECMAScript design principles

答案 3 :(得分:15)

仍有一个参数来引用该函数,而不必对其名称进行硬编码。

答案 4 :(得分:0)

只是一个扩展。 "这个"的价值递归期间的变化。在以下(修改)示例中,factorial获取{foo:true}对象。

[1,2,3,4,5].map(function factorial(n) {
  console.log(this);
  return (!(n>1))? 1 : factorial(n-1)*n;
},     {foo:true}     );

第一次调用的factorial获取对象,但对于递归调用则不然。