
許多js環境都提供檢查調用棧的功能。調用棧是指目前正在執行的活動函數鍊。在某些舊的宿主環境中,每個arguments對象含有兩個額外的屬性:arguments.callee和arguments.caller。前者指向使用該arguments對象被調用的函數。後者指向調用該arguments對象被調用
許多js環境都提供檢查調用棧的功能。調用棧是指目前正在執行的活動函數鍊。在某些舊的宿主環境中,每個arguments對象含有兩個額外的屬性:arguments.callee和arguments.caller。前者指向使用該arguments對象被調用的函數。後者指向調用該arguments對象被調用的函數的函數。許多環境支援arguments.callee,但它除了允許匿名函數遞歸地引用自身之外,沒有更多的用途了。(高3中認為使用arguments.callee可以解除函數體内的代碼和函數名之間的耦合,看來也不是完全沒有用的)
圖示
下面是一個簡單的圖示,可以容易了解arguments的callee,caller,及函數的caller
var factorial=(function(n){
return (n<=1)?1:(n*arguments.callee(n-1));
})
但這個也是特别的有用,可以使用函數名來引用函數自身
function factorial(n){
return (n<=1)?1:(n*factorial(n-1));
}
arguments.caller屬性更為強大。它指向的是使用該arguments對象調用函數的函數。出于安全考慮,大多數環境已經移除了此特性,是以用它時要檢測一下才行。許多JS環境也提供了一個相似的函數對象屬性--非标準但普遍适應的caller屬性。它指向函數最近的調用者。
function revealCaller(){
return revealCaller.caller;
}
function start(){
return revealCaller();
}
start()===start;//true;
可以利用該屬性擷取一個提供目前調用棧快照的資料結構。建構一個棧跟蹤:
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();//[f1(), f2()]
脆弱性,當某個函數在調用棧中出現不止一次,那麼棧檢查邏輯将會陷入循環。
function f(n){
return n===0?getCallStack():f(n-1);
}
var trace=f(1);//
問題出在哪?由于函數f遞歸地調用其自身,是以其caller屬性會自動更新,指回到函數f。是以,函數getCallStack會陷入無限地查找函數f的循環之中。即使我們試圖檢測該循環,但在函數f調用其自身之前也沒有關于哪個函數調用了它的資訊。因為其他調用棧的資訊已經丢失了。
這些棧檢查屬性都是非标準的,在移植性或适用性上很受限制。在ES5的嚴格模式的函數中,它們是被禁止使用的。試圖擷取嚴格函數或arguments對象的caller或callee屬性都将報錯。
function f(){
'use strict';
return f.caller;
}
f();//Uncaught TypeError: 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.(…)
最好的政策是完全避免棧檢查。如果檢查棧的理由完全是為了測試,那麼更為可靠的方式是使用互動式的調試器。
提示
- 避免使用非标準的arguments.caller和arguments.callee屬性,它們不具備良好的移植性
- 避免使用非标準的函數對象caller屬性,因為在包含全部棧資訊方面,它是不可靠的
附錄:這個沒附錄,放水一篇,因為這節實在沒什麼可寫的。
版權聲明
翻譯的文章,版權歸原作者所有,隻用于交流與學習的目的。
原創文章,版權歸作者所有,非商業轉載請注明出處,并保留原文的完整連結。