arguments.callee 从 ES5 严格模式中被移除掉的一个主要原因是递归调用会获取到一个不同的 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();
另外一个被废弃的特性是 arguments.callee.caller,具体点说则是 Function.caller。为什么? 额,在任何一个时间点,你能在堆栈中找到任何函数的最深层的调用者,也正如我在上面提到的,在调用堆栈有一个单一重大影响:不可能做大量的优化,或者有更多更多的困难。比如,如果你不能保证一个函数 f 不会调用一个未知函数,它就绝不可能是内联函数 f。基本上这意味着内联代码中积累了大量防卫代码:
function f (a, b, c, d, e) { return a ? b * c : d * e; }
如果JavaScript解释器不能保证所有提供的参数数量在被调用的时候都存在,那么它需要在行内代码插入检查,或者不能内联这个函数。现在在这个特殊例子里一个智能的解释器应该能重排检查而更优,并检查任何将不用到的值。然而在许多的情况里那是不可能的,也因此它不能够内联。
安全问题:
可以利用这个属性来得到当前调用栈的信息:
function getCallStack() {
var stack = [];
for (var f = getCallStack.caller; f; f = f.caller) {
stack.push(f);
}
return stack;
}
对于简单的调用关系,上述确实能够得到调用栈的信息:
function f1() {
return getCallStack();
}
function f2() {
return f1();
}
var trace = f2();
trace; // [f1, f2]
但是当一个函数在调用栈中出现不止一次时,就会发生问题了,比如下面的代码会产生一个死循环:
function f(n) {
return n === 0 ? getCallStack() : f(n - 1);
}
var trace = f(1); // infinite loop
原因在于,当发生递归调用时,函数自身会被赋值给它的caller属性。因此getCallStack中的for循环的终止条件f永远不会为false:
for (var f = getCallStack.caller; f; f = f.caller) {
stack.push(f);
}
参考文献:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments/callee#为什么arguments.callee从ES5严格模式中移除掉
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
https://github.com/airbnb/javascript/issues/52