一個(gè)關(guān)于原型鏈的探索

begin: 20170702
version: 20170724

原型鏈基礎(chǔ)

看了《javascript高級程序設(shè)計(jì)》關(guān)于對象繼承方面的內(nèi)容,對原型鏈有了一定理解:

  • 構(gòu)造函數(shù)也是函數(shù),函數(shù)都是對象,構(gòu)造函數(shù)作為一個(gè)函數(shù)類型的對象,有一個(gè)prototype屬性,它引用一個(gè)對象,我們稱其為構(gòu)造函數(shù)的原型對象;
  • 構(gòu)造函數(shù)的原型對象中有一個(gè) constructor屬性,它反過來引用構(gòu)造函數(shù);
  • 如果用構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例對象,那么這個(gè)實(shí)例對象會(huì)和構(gòu)造函數(shù)的原型對象產(chǎn)生聯(lián)系(而不是構(gòu)造函數(shù));具體來說,這個(gè)實(shí)例內(nèi)部有一個(gè)[[Prototype]]屬性,它指向構(gòu)造函數(shù)的原型對象。(為避免歧義,我們之后把這個(gè)原型對象稱作構(gòu)造函數(shù)的原型對象、實(shí)例的原型
  • [[Prototype]]屬性不能在實(shí)例中直接訪問,但可以借助一個(gè)es5方法Object.getPrototypeof();
  • 如果一個(gè)實(shí)例的原型本身又是另一種類型的實(shí)例,那么這樣循環(huán)迭代,一個(gè)實(shí)例的原型就可以一層層上推,形成原型鏈,最終那個(gè)原型是一個(gè)Object對象。

問題

  1. 如果我有個(gè)對象,能不能把這個(gè)對象的原型鏈分析展現(xiàn)出來呢,特別地,函數(shù)作為對象自身的原型鏈?zhǔn)窃鯓拥哪??(我們知道函?shù)定義也可以通過調(diào)用構(gòu)造函數(shù)Function實(shí)現(xiàn),這個(gè)構(gòu)造函數(shù)又是怎么和 最終的Object對象聯(lián)系的呢?)

  2. 上面提到的方法Object.getPrototypeof()在什么地方,類似的還如Array.isArray()顯然它不在普通對象的原型鏈中,否則通過繼承Object,對象實(shí)例、數(shù)組實(shí)例本身就應(yīng)該能用相應(yīng)的方法。

問題一編程探索

程序編寫

為理解第一個(gè)問題,首先全局環(huán)境下定義一個(gè)簡單函數(shù)baseFunc,然后建立另一函數(shù)testProto,它以傳入的參數(shù)baseObj對象為起點(diǎn),搜索該對象相關(guān)的的原型、函數(shù)的原型對象、構(gòu)造函數(shù),把這些對象搜集放在一個(gè)數(shù)組中,并在調(diào)試窗口輸出對象間的關(guān)系。

以下是testProto函數(shù)的思路:

  1. 首先將參數(shù)對象初始化為數(shù)組的第一個(gè)元素,然后將其傳入內(nèi)部遞歸閉包,閉包中先判斷對象__proto__(原型,受chrome,safari,firefox支持)、prototype(構(gòu)造函數(shù)的原型對象)、constructor三個(gè)屬性是否存在,把存在的屬性的值(是一個(gè)對象)依次放入數(shù)組,然后以此將存在的屬性值作為參數(shù)遞歸地傳入閉包中。

  2. 需要注意的是將存在的屬性放入數(shù)組時(shí),要檢查它是否因?yàn)橹巴瑫r(shí)被另一個(gè)對象的三個(gè)屬性之一引用而已經(jīng)被放入過數(shù)組了;遞歸調(diào)用前也要檢查它們之前是否已經(jīng)被調(diào)用過(如果已經(jīng)被調(diào)用過卻再次調(diào)用,那么可能陷入無限遞歸的死循環(huán))。

下面是代碼


window.onload = function(){
    testProto(baseFunc);
}

function baseFunc(){}

function testProto(baseObj){
//以baseObj對象為起點(diǎn),搜索原型、函數(shù)的原型對象、構(gòu)造函數(shù)
    var objs = new Array(), //存儲(chǔ)相關(guān)對象
        max = 0;    //objs中存儲(chǔ)的對象最大索引
    
    objs[0] = baseObj;  

    (function(obj){
        if(max < 100){
            //避免無窮遞歸(如果真的沿著原型鏈可以無限走下去的話)
            
            if(obj.__proto__ && objs.indexOf(obj.__proto__) < 0){
                //具有__proto__屬性(obj的原型),
                //并且該原型之前沒有添加到數(shù)組里
                //(考慮到它可能同時(shí)作為其他對象的屬性被引用著)
                objs[++max] = obj.__proto__;
            }
            if(obj.prototype && objs.indexOf(obj.prototype) < 0){
                objs[++max] = obj.prototype;
            }
            if(obj.constructor && objs.indexOf(obj.constructor) < 0){
                objs[++max] = obj.constructor;
            }

            if(obj.__proto__ 
            && objs.indexOf(obj.__proto__) > objs.indexOf(obj)){
                //該屬性存在,
                //其引用的對象已經(jīng)被添加到數(shù)組里,
                //而且添加時(shí)間晚于父元素obj
                //(早于父元素是可能的,此時(shí)必定已經(jīng)被處理過了)。
                arguments.callee.call(this,obj.__proto__);  
                //遞歸地處理屬性對象
            }
            if(obj.prototype 
            && objs.indexOf(obj.prototype) > objs.indexOf(obj)){
                arguments.callee.call(this,obj.prototype);
            }
            if(obj.constructor 
            && objs.indexOf(obj.constructor) > objs.indexOf(obj)){
                arguments.callee.call(this,obj.constructor);
            }

        }
    })(objs[0]);

    //給出各對象類型,如果是函數(shù),給出函數(shù)名
    for(i = 0; i <= max; i++){
        console.log(i + " is " + (typeof objs[i]) 
            + " " + ((typeof objs[i] === "function")?objs[i].name:""));
    }

    //次序給出各個(gè)對象的原型、原型對象(如果這個(gè)對象是個(gè)函數(shù))、constructor屬性
    for(i = 0; i <= max; i++){
        console.log(i + "==========");
        if(objs[i].__proto__){
            console.log("__proto__:" + objs.indexOf(objs[i].__proto__));
        }
        if(objs[i].prototype){
            console.log("prototype:" + objs.indexOf(objs[i].prototype));
        }
        if(objs[i].constructor){
            console.log("constructor:" + objs.indexOf(objs[i].constructor));
        }
    }

    //部分核查程序的正確性:數(shù)組中沒有出現(xiàn)對象被重復(fù)統(tǒng)計(jì)處理的情況
    for(i = 0; i <= max; i++){
        if(objs.lastIndexOf(objs[i]) === objs.indexOf(objs[i])){
            console.log(true);
        }else{
            console.log(false);
        }
    }
}

輸出

程序在控制臺(tái)的輸出如下:



前面幾行首先以此輸出數(shù)組中保存的相關(guān)對象,如果對象是函數(shù),給給出相應(yīng)的函數(shù)名稱;然后依次給出各對象三個(gè)屬性存在的屬性的值在數(shù)組中的索引。

結(jié)果整理分析

把以上程序在chrome瀏覽器中運(yùn)行,利用控制臺(tái)的輸出,整理得到下面這張圖:


  1. 圖中0、1、3、5都是函數(shù),2、4是對象。0就是我們定義的baseFunc函數(shù),它的原型對象是2,它作為一個(gè)對象有原型1,對象1是一個(gè)函數(shù),但是我們未能獲得它的函數(shù)名,我們把它記為函數(shù)X,對象1的原型是對象4。
  2. 對象3就是構(gòu)造函數(shù)Function,對象5就是構(gòu)造函數(shù)Object。
  3. 圖中所有對象的原型鏈走到頂端都是對象4,它就是被ECMAScript中所有對象繼承的Object對象。
  4. 圖中的函數(shù)都繼承自函數(shù)X(對象1),因此我們基本可以推論說函數(shù)就是一個(gè)X類型;
  5. 由于constructor屬性可能是繼承的,所以圖中的這個(gè)關(guān)系不一定很準(zhǔn)確,實(shí)際上我在調(diào)試窗口中查找,對象0,1,5上都沒有顯式的constructor屬性。

問題二的解答

在解決問題一的過程中,我發(fā)現(xiàn)可以在瀏覽器調(diào)試窗口下通過window對象和添加watch對象,然后一路追蹤對象的原型、原型對象、consctructor等屬性看到對象的原型鏈,然而這畢竟還是有點(diǎn)問題,層次太深以后就容易亂,特別是基本的那幾個(gè)對象之間有著循環(huán)引用;另一個(gè)問題是兩個(gè)對象都寫作Object,我們沒有把握判斷它們是否真的就是一個(gè)對象,為此上面的編程還是有不少幫助的。

借助如上的調(diào)試手段和原型探索程序,我們可以很容易地在window>baseFunc>__proto__(函數(shù)X)>__proto__(基本對象Object)>constrctor(構(gòu)造函數(shù)Object)中找到了getPrototypeOf()方法,它是構(gòu)造函數(shù)Object的一個(gè)屬性,所以寫作Object.getPrototypeOf()(其實(shí)早該想到,只是這就被明確地證實(shí)了,這就好比構(gòu)造函數(shù)Object作為函數(shù)具有arguments屬性一樣),由于它不是基本對象Object的屬性,所以沒有被普通對象繼承。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容