arguments.callee

JavaScript

arguments.callee 是一个不应该被使用的 API,在严格模式下使用会直接报错。这里仅仅是作为了解,记录一下该 API 的作用。

在早期的 JavaScript 版本中,不允许写带名字的函数表达式,在这种情况下,如果需要做递归调用,就无法显式得指明需要调用的函数名称。arguments.callee 这个值,指向了当前被调用的函数本身,因此可以在匿名函数递归调用中被使用。举例来说,在早期的 JavaScript 中,Array.prototype.map 函数给定的回调函数只能是匿名的,如果要实现一个阶乘函数,只能这么写:

[1, 2, 3].map(function (num) {
  if (n > 1) return arguments.callee(num - 1) * num;
  return 1;
});

然而,arguments.callee 的调用会导致 this 的指向出现问题(具体见 MDN),使用起来比较危险。

在 ECMAScript 3 中已经支持了带函数名的表达式,因此上面的代码可以简单的改写为一下这种正常的写法:

[1, 2, 3].map(function factorial(num) {
  if (n > 1) return factorial(num - 1) * num;
  return 1;
});

换句话说,只需要给函数指定名称,就可以规避绝大多数的 arguments.callee 使用了(注:匿名函数/箭头函数无法指定名称,但同时规范也明确了匿名函数中没有 arguments)。

MDN 给出了一个 arguments.callee 无法替换的场景:

function createPerson(sIdentity) {
  var oPerson = new Function('alert(arguments.callee.identity);');
  oPerson.identity = sIdentity;
  return oPerson;
}

var john = createPerson('John Smith');

john();

这里的函数 oPerson 是通过 new Function 创建的。在字符串内无法“得知”函数会被赋值的名称,因此只能通过 arguments.callee 去获取。在某些非常特殊的业务场景中,可能会有需求将某些表达式通过字符串进行存储,并通过 new Function 构建执行。这种时候,使用 arguments.callee 获取数据类似于传参。当然,如果只是传参的需求,其实可以写成:

const script = 'alert(arg.identity)';
function createPerson(identity) {
  const closure = new Function([
    'const arg = arguments[0];',
    `return function () { ${script} }`,
  ].join('\n'));
  return closure({ identity });
}

var john = createPerson('John Smith');

john();