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

begin: 20170702
version: 20170724

原型鏈基礎(chǔ)

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

  • 構(gòu)造函數(shù)也是函數(shù)晶通,函數(shù)都是對象,構(gòu)造函數(shù)作為一個函數(shù)類型的對象鳖宾,有一個prototype屬性,它引用一個對象贰镣,我們稱其為構(gòu)造函數(shù)的原型對象酒繁;
  • 構(gòu)造函數(shù)的原型對象中有一個 constructor屬性导梆,它反過來引用構(gòu)造函數(shù)轨淌;
  • 如果用構(gòu)造函數(shù)創(chuàng)建一個實例對象迂烁,那么這個實例對象會和構(gòu)造函數(shù)的原型對象產(chǎn)生聯(lián)系(而不是構(gòu)造函數(shù));具體來說递鹉,這個實例內(nèi)部有一個[[Prototype]]屬性盟步,它指向構(gòu)造函數(shù)的原型對象。(為避免歧義躏结,我們之后把這個原型對象稱作構(gòu)造函數(shù)的原型對象址芯、實例的原型
  • [[Prototype]]屬性不能在實例中直接訪問,但可以借助一個es5方法Object.getPrototypeof()窜觉;
  • 如果一個實例的原型本身又是另一種類型的實例,那么這樣循環(huán)迭代北专,一個實例的原型就可以一層層上推禀挫,形成原型鏈,最終那個原型是一個Object對象拓颓。

問題

  1. 如果我有個對象语婴,能不能把這個對象的原型鏈分析展現(xiàn)出來呢,特別地驶睦,函數(shù)作為對象自身的原型鏈?zhǔn)窃鯓拥哪嘏樽螅浚ㄎ覀冎篮瘮?shù)定義也可以通過調(diào)用構(gòu)造函數(shù)Function實現(xiàn),這個構(gòu)造函數(shù)又是怎么和 最終的Object對象聯(lián)系的呢场航?)

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

問題一編程探索

程序編寫

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

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

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

  2. 需要注意的是將存在的屬性放入數(shù)組時男应,要檢查它是否因為之前同時被另一個對象的三個屬性之一引用而已經(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對象為起點,搜索原型牲迫、函數(shù)的原型對象耐朴、構(gòu)造函數(shù)
    var objs = new Array(), //存儲相關(guān)對象
        max = 0;    //objs中存儲的對象最大索引
    
    objs[0] = baseObj;  

    (function(obj){
        if(max < 100){
            //避免無窮遞歸(如果真的沿著原型鏈可以無限走下去的話)
            
            if(obj.__proto__ && objs.indexOf(obj.__proto__) < 0){
                //具有__proto__屬性(obj的原型),
                //并且該原型之前沒有添加到數(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ù)組里筛峭,
                //而且添加時間晚于父元素obj
                //(早于父元素是可能的,此時必定已經(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:""));
    }

    //次序給出各個對象的原型檩禾、原型對象(如果這個對象是個函數(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)計處理的情況
    for(i = 0; i <= max; i++){
        if(objs.lastIndexOf(objs[i]) === objs.indexOf(objs[i])){
            console.log(true);
        }else{
            console.log(false);
        }
    }
}

輸出

程序在控制臺的輸出如下:



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

結(jié)果整理分析

把以上程序在chrome瀏覽器中運行戏售,利用控制臺的輸出啦辐,整理得到下面這張圖:


  1. 圖中0、1蜈项、3芹关、5都是函數(shù),2紧卒、4是對象侥衬。0就是我們定義的baseFunc函數(shù),它的原型對象是2跑芳,它作為一個對象有原型1轴总,對象1是一個函數(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ù)就是一個X類型吨瞎;
  5. 由于constructor屬性可能是繼承的,所以圖中的這個關(guān)系不一定很準(zhǔn)確穆咐,實際上我在調(diào)試窗口中查找颤诀,對象0,1对湃,5上都沒有顯式的constructor屬性崖叫。

問題二的解答

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筐高,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丑瞧,更是在濱河造成了極大的恐慌柑土,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绊汹,死亡現(xiàn)場離奇詭異稽屏,居然都是意外死亡,警方通過查閱死者的電腦和手機西乖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門狐榔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坛增,“玉大人,你說我怎么就攤上這事荒叼〗钨耍” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵被廓,是天一觀的道長坏晦。 經(jīng)常有香客問我,道長嫁乘,這世上最難降的妖魔是什么昆婿? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蜓斧,結(jié)果婚禮上仓蛆,老公的妹妹穿的比我還像新娘。我一直安慰自己挎春,他們只是感情好看疙,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著直奋,像睡著了一般能庆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脚线,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天搁胆,我揣著相機與錄音,去河邊找鬼邮绿。 笑死渠旁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的船逮。 我是一名探鬼主播顾腊,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挖胃!你這毒婦竟也來了投慈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冠骄,失蹤者是張志新(化名)和其女友劉穎伪煤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凛辣,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡抱既,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了扁誓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片防泵。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚀之,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捷泞,到底是詐尸還是另有隱情足删,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布锁右,位于F島的核電站失受,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏咏瑟。R本人自食惡果不足惜拂到,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望码泞。 院中可真熱鬧兄旬,春花似錦、人聲如沸余寥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宋舷。三九已至绪撵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肥缔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工汹来, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留续膳,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓收班,卻偏偏與公主長得像坟岔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摔桦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容