JavaScript:遍歷原型鏈,調用棧,作用域鏈


在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來實現的.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM