在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])) } }) })() })() })() ----------------------------- //函數fun3 arguments:[object Arguments] a:3 fun3:fun3 ----------------------------- //函數fun2 arguments:[object Arguments] a:2 fun2:fun2 ----------------------------- //函數fun1 arguments:[object Arguments] a:1 fun1:fun1 ----------------------------- //全局上下文 a:0 getScopeChain:getScopeChain
另外,如果是在Firefox的特權代碼中(chrome上下文),還可以使用Debugger API來獲取到各種引擎內部隱藏着的數據,Firebug中的以及Firefox自帶的調試器,都是用這些API來實現的.