在JavaScript中,有三種常見的鏈式結構:原型鏈(Prototype Chain),調用棧(Call Stack),範圍鏈(Scope Chain).本文並不準備講這些概念的基礎知識,而是要給出如何遍曆這三種鏈結構的方法,從而加深理解.
遍曆原型鏈
在JavaScript中,任何對象都有自己的原型鏈.原型鏈是由一系列對象加上最後的null組成的.如果還沒掌握相關基礎知識,可以看看我在MDN上翻譯的繼承與原型鏈一文.遍曆函數如下:
function getPrototypeChain(obj) { var protoChain = []; while (obj = Object.getPrototypeOf(obj)) { protoChain.push(obj); } protoChain.push(null); return protoChain;}
嘗試執行一下
>getPrototypeChain(new String(""))[String, Object, null] //依次是String.prototype,Object.prototype,null >getPrototypeChain(function(){})[function Empty() {}, Object, null] //依次是Function.prototype,Object.prototype,null
這個函數是在我以前寫的一篇文章JavaScript:我對原型鏈的理解中給出的.
遍曆調用棧
在JavaScript中,調用棧就是一系列的函數,表明當前函數是由哪些上層函數調用的.遍曆函數如下:
function getCallStack() { var stack = []; var fun = getCallStack; while (fun = fun.caller) { stack.push(fun) } return stack}
該函數用到了非標準的caller屬性,不過主流瀏覽器都支援它.嘗試執行一下:
function a() { b()}function b() { c()}function c() { alert(getCallStack().map(function (fun) { return fun.name //使用了非標準的name屬性 })) }a() //彈出c,b,a
b() //彈出c,b
在調試工具中,我們可以直接使用console.trace()來列印出調用棧.在遞迴調用中,如果調用棧的長度過長,引擎就會拋出異常"too much recursion".到底多長是上限,不同的引擎不同的作業系統環境這個值是不同的.可以使用下面這個函數運算式擷取到這個上限值:
> (function(i){try{(function m(){++i&&m()}())}catch(e){return i}})(0)50761
遍曆範圍鏈
範圍鏈是由一系列執行內容(Execution context)中的使用中的物件(Activation object)加最後的全域對象組成的.使用中的物件是一個抽象實體(Abstract Entity),它是由引擎內部來管理的,並不能通過JavaScript來訪問.看不到,摸不著,所以這些知識就很難理解.
不過在Mozilla的引擎中,有一個魔法屬性__parent__可以擷取到函數執行時的使用中的物件.只是在SpiderMonkey中,該屬性已經被刪除了(Firefox 4開始).不過在Mozilla的另外一個JavaScript引擎Rhino(Java編寫)上,還可以使用這個特殊屬性.遍曆代碼如下:
function getScopeChain(fun) { var scopeChain = []; while (fun = fun.__parent__) { scopeChain.push(fun);
} return scopeChain;
}
嘗試執行一下:
var a = 0;(function fun1() { var a = 1; (function fun2() { var a = 2; (function fun3() { var a = 3; getScopeChain(function () {}).map(function (obj) { print("-----------------------------") for(var i in obj){ print(i + ":" + (obj[i].name?obj[i].name:obj[i])) } }) })() })()})()----------------------------- //函數fun3arguments:[object Arguments] a:3fun3:fun3----------------------------- //函數fun2arguments:[object Arguments]a:2fun2:fun2----------------------------- //函數fun1arguments:[object Arguments]a:1fun1:fun1----------------------------- //全域上下文a:0getScopeChain:getScopeChain
另外,如果是在Firefox的特權代碼中(chrome上下文),還可以使用Debugger API來擷取到各種引擎內部隱藏著的資料,Firebug中的以及Firefox內建的調試器,都是用這些API來實現的.