arguments.callee 從 ES5 嚴格模式中被移除掉的一個主要原因是遞歸調(diào)用會獲取到一個不同的 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。為什么? 額,在任何一個時間點,你能在堆棧中找到任何函數(shù)的最深層的調(diào)用者,也正如我在上面提到的,在調(diào)用堆棧有一個單一重大影響:不可能做大量的優(yōu)化,或者有更多更多的困難。比如,如果你不能保證一個函數(shù) f 不會調(diào)用一個未知函數(shù),它就絕不可能是內(nèi)聯(lián)函數(shù) f。基本上這意味著內(nèi)聯(lián)代碼中積累了大量防衛(wèi)代碼:
function f (a, b, c, d, e) { return a ? b * c : d * e; }
如果JavaScript解釋器不能保證所有提供的參數(shù)數(shù)量在被調(diào)用的時候都存在,那么它需要在行內(nèi)代碼插入檢查,或者不能內(nèi)聯(lián)這個函數(shù)。現(xiàn)在在這個特殊例子里一個智能的解釋器應該能重排檢查而更優(yōu),并檢查任何將不用到的值。然而在許多的情況里那是不可能的,也因此它不能夠內(nèi)聯(lián)。
安全問題:
可以利用這個屬性來得到當前調(diào)用棧的信息:
function getCallStack() {
var stack = [];
for (var f = getCallStack.caller; f; f = f.caller) {
stack.push(f);
}
return stack;
}
對于簡單的調(diào)用關系,上述確實能夠得到調(diào)用棧的信息:
function f1() {
return getCallStack();
}
function f2() {
return f1();
}
var trace = f2();
trace; // [f1, f2]
但是當一個函數(shù)在調(diào)用棧中出現(xiàn)不止一次時,就會發(fā)生問題了,比如下面的代碼會產(chǎn)生一個死循環(huán):
function f(n) {
return n === 0 ? getCallStack() : f(n - 1);
}
var trace = f(1); // infinite loop
原因在于,當發(fā)生遞歸調(diào)用時,函數(shù)自身會被賦值給它的caller屬性。因此getCallStack中的for循環(huán)的終止條件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