arguments.callee

已弃用:此功能不再推荐。尽管某些浏览器可能仍然支持它,但它可能已从相关的 Web 标准中删除,可能正在被删除,或者可能仅出于兼容性目的而保留。避免使用它,并在可能的情况下更新现有代码;请参阅此页面底部的兼容性表,以指导您的决策。请注意,此功能可能随时停止工作。

注意:严格模式中访问arguments.callee将抛出TypeError。如果函数必须引用自身,则要么为函数表达式命名,要么使用函数声明

arguments.callee数据属性包含参数所属的当前正在执行的函数。

对当前正在执行的函数的引用。

arguments.callee的属性属性
可写
可枚举
可配置

注意:callee仅在非严格函数中具有简单参数(在这种情况下,arguments对象也自动同步)时才是数据属性。否则,它是一个访问器属性,其 getter 和 setter 都抛出TypeError

描述

calleearguments对象的属性。它可以用来在该函数的函数体内部引用当前正在执行的函数。当函数的名称未知时,这很有用,例如在没有名称的函数表达式(也称为“匿名函数”)中。

(以下文本大部分改编自olliej 在 Stack Overflow 上的回答

早期版本的 JavaScript 不允许命名函数表达式,因此您无法创建递归函数表达式。

例如,此语法有效

js
function factorial(n) {
  return n <= 1 ? 1 : factorial(n - 1) * n;
}

[1, 2, 3, 4, 5].map(factorial);

但是

js
[1, 2, 3, 4, 5].map(function (n) {
  return n <= 1 ? 1 : /* what goes here? */ (n - 1) * n;
});

没有。为了解决这个问题,添加了arguments.callee,以便您可以执行以下操作

js
[1, 2, 3, 4, 5].map(function (n) {
  return n <= 1 ? 1 : arguments.callee(n - 1) * n;
});

但是,arguments.callee的设计存在多个问题。第一个问题是递归调用将获得不同的this值。例如

js
const global = this;

const sillyFunction = function (recursed) {
  if (this !== global) {
    console.log("This is:", this);
  } else {
    console.log("This is the global");
  }

  if (!recursed) {
    return arguments.callee(true);
  }
};

sillyFunction();
// This is the global
// This is: [object Arguments]

此外,对arguments.callee的引用使得在一般情况下内联和尾递归成为不可能。(您可以在某些情况下通过跟踪等方式实现它,但即使是最优的代码也由于不必要的检查而次优。)

ECMAScript 3 通过允许命名函数表达式解决了这些问题。例如

js
[1, 2, 3, 4, 5].map(function factorial(n) {
  return n <= 1 ? 1 : factorial(n - 1) * n;
});

这有许多好处

  • 该函数可以像任何其他函数一样从代码内部调用
  • 它不会在外部作用域中创建变量(除了 IE 8 及以下版本
  • 它比访问 arguments 对象具有更好的性能

严格模式已禁止其他泄漏堆栈信息的属性,例如函数的caller属性。这是因为查看调用堆栈只有一个主要影响:它使大量优化变得不可能或更加困难。例如,如果您不能保证函数f不会调用未知函数,则无法内联f

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

如果 JavaScript 解释器无法保证在调用时所有提供的参数都是数字,则它需要在内联代码之前插入所有参数的检查,或者无法内联函数。这意味着任何可能已被简单内联的调用站点都会累积大量保护措施。现在,在这种特定情况下,一个智能解释器应该能够重新排列检查以使其更优化,并且不检查任何不会被使用的值。但是,在许多情况下,这根本不可能,因此内联变得不可能。

示例

在匿名递归函数中使用 arguments.callee

递归函数必须能够引用自身。通常,函数通过其名称引用自身。但是,匿名函数(可以通过函数表达式Function构造函数创建)没有名称。因此,如果没有可访问的变量引用它,则函数唯一可以引用自身的方法是通过arguments.callee

以下示例定义了一个函数,该函数依次定义并返回一个阶乘函数。此示例不太实用,并且几乎没有案例无法通过命名函数表达式实现相同的结果。

js
function create() {
  return function (n) {
    if (n <= 1) {
      return 1;
    }
    return n * arguments.callee(n - 1);
  };
}

const result = create()(5); // returns 120 (5 * 4 * 3 * 2 * 1)

使用 Y 组合子的匿名函数递归

尽管函数表达式现在可以命名,但箭头函数始终保持匿名,这意味着它们在未首先分配给变量的情况下无法引用自身。幸运的是,在 Lambda 演算中,有一个非常好的解决方案允许函数既是匿名的又是自引用的。该技术称为Y 组合子。在这里,我们不会解释如何工作,只解释可以工作。

js
// The Y-combinator: a utility function!
const Y = (hof) => ((x) => x(x))((x) => hof((y) => x(x)(y)));

console.log(
  [1, 2, 3, 4, 5].map(
    // Wrap the higher-order function in the Y-combinator
    // "factorial" is not a function's name: it's introduced as a parameter
    Y((factorial) => (n) => (n <= 1 ? 1 : factorial(n - 1) * n)),
  ),
);
// [ 1, 2, 6, 24, 120 ]

注意:此方法为每次迭代分配一个新的闭包,这可能会显着增加内存使用量。它仅用于演示可能性,但在生产环境中应避免使用。请改用临时变量或命名函数表达式。

规范

规范
ECMAScript 语言规范
# sec-arguments-exotic-objects

浏览器兼容性

BCD 表仅在浏览器中加载。

另请参阅