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

IT技术 javascript ecma262
2021-02-02 00:40:44

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

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

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

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

4个回答

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

  • 它的性能更高(访问参数对象很昂贵)。

oop,

刚刚意识到除了问题所涉及的其他所有问题arguments.callee.caller,或者更具体地说Function.caller

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

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

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

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

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

你是说它被贬低只是因为它很难优化?这有点傻。
2021-03-23 00:40:44
不,我列出了一些原因,除了它使优化变得困难(尽管在一般历史上已经表明,难以优化的事物也具有人们难以遵循的语义)
2021-04-02 00:40:44
)我仍然没有看到逻辑。在任何具有一流函数的语言中,能够定义一个可以引用自身而不必知道 i 的函数体具有明显的value
2021-04-03 00:40:44
说法有点假,其值可以通过调用来设置,如果它是很重要的。通常不使用它(至少,我在递归函数中从未遇到过它的问题)。按名称调用函数也有同样的问题,this所以我认为这与被调用者的好坏无关此外,被调用者调用者仅在严格模式下“弃用”(ECMAscript ed 5,2009 年 12 月),但我想这在 2008 年 olliej 发布时并不为人所知。
2021-04-06 00:40:44
RobG 指出了这一点,但我认为这不是很清楚:使用命名函数递归只会保留thisif的值this是全局范围。在所有其他情况下,第一次递归调用后的值this 改变,所以我认为你的答案中提到保存的this部分并不是真正有效的。
2021-04-06 00:40:44

arguments.callee.caller不是过时了,但它确实利用的特性。只会给你一个对当前函数的引用)Function.callerarguments.callee

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

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

你必须到 MDNarguments.callee超链接说它在严格模式下被删除。这与弃用不一样吗?
2021-03-12 00:40:44
虽然,arguments.callee在严格模式下是禁止的。这也让我很伤心,但还是不要再用了。
2021-03-13 00:40:44
Function.caller 在严格模式下是不允许的:Uncaught TypeError: 'caller'、'callee' 和 'arguments' 属性可能无法在严格模式函数或调用它们的参数对象上访问
2021-03-29 00:40:44
这是对弃用和不弃用的最好解释,非常有用。有关 Function.caller 不能做什么的一个很好的例子(获取递归函数的堆栈跟踪),请参阅developer.mozilla.org/en/JavaScript/Reference/Global_Objects/...
2021-04-03 00:40:44
请注意,arguments.callee.caller在 ES5 严格模式中已弃用:“另一个已弃用的功能是 arguments.callee.caller,或者更具体地说是 Function.caller。” 来源
2021-04-04 00:40:44

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

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

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

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

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

这比

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

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

有时最简单的调试方法是使用 .caller()。在这种情况下,命名函数将无济于事 - 您正在尝试确定哪个函数正在调用。
2021-03-13 00:40:44
你能描述为什么使用命名函数更好。永远不需要在匿名函数中使用 callee 吗?
2021-03-15 00:40:44
更好地定义。例如,IE6-8有命名函数的怪癖,而arguments.callee 工作。
2021-03-19 00:40:44
如果您在匿名函数中使用 callee,那么您的函数不应该是匿名的。
2021-03-20 00:40:44
除了 IE6-8 的怪癖外,它还使代码紧密耦合。如果对象和/或函数的名称是硬编码的,那么正如 ardsasd 和 rsk82 所提到的,存在重大的重构危险,这种危险只会随着代码库大小的增加而增加。单元测试是一道防线,我使用它们,但对于这个硬编码问题,它们仍然不是让我个人感到满意的答案。
2021-03-30 00:40:44

只是一个扩展。“this”的值在递归过程中会发生变化。在以下(修改后的)示例中, 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}     );

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

因为你做错了。如果this需要维护,写factorial.call(this, n-1)我在写递归代码时发现的,通常没有this,或者this指的是树中的某个节点,实际上它改变是好的。
2021-03-19 00:40:44