220

为什么arguments.callee.callerJavaScript 中不推荐使用该属性?

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

是否有充分的理由将这个有价值的功能置于边缘?

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

4

4 回答 4

259

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 对象很昂贵)。

哎呀,

刚刚意识到除了其他所有问题之外,问题是关于arguments.callee.caller,或者更具体地说Function.caller

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

例如。如果我们不能保证一个函数f不会调用未知函数,那么就不可能内联f。基本上,这意味着任何可能被微不足道内联的调用站点都会积累大量的守卫,采取:

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

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

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

于 2008-10-25T01:51:40.203 回答
89

arguments.callee.caller没有被弃用,尽管它确实使用了该属性。(只会给你一个当前函数的参考)Function.callerarguments.callee

  • Function.caller,尽管根据 ECMA3 是非标准的,但已在所有当前主要浏览器中实现。
  • arguments.caller 弃用,取而代之的是,并且在当前的一些主要浏览器(例如 Firefox 3)中未实现。Function.caller

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

于 2009-08-26T15:31:21.707 回答
29

使用命名函数比使用 arguments.callee 更好:

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

好于

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

命名函数将可以通过caller属性访问其调用者:

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

这比

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

弃用是由于当前的 ECMAScript设计原则

于 2008-09-19T17:38:12.813 回答
0

只是一个扩展。“this”的值在递归期间发生变化。在以下(修改后的)示例中,阶乘获取 {foo:true} 对象。

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

第一次调用的阶乘获取对象,但递归调用并非如此。

于 2015-01-22T08:44:14.787 回答