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
對象拓颓。
問題
如果我有個對象语婴,能不能把這個對象的原型鏈分析展現(xiàn)出來呢,特別地驶睦,函數(shù)作為對象自身的原型鏈?zhǔn)窃鯓拥哪嘏樽螅浚ㄎ覀冎篮瘮?shù)定義也可以通過調(diào)用構(gòu)造函數(shù)
Function
實現(xiàn),這個構(gòu)造函數(shù)又是怎么和 最終的Object
對象聯(lián)系的呢场航?)上面提到的方法
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ù)的思路:
首先將參數(shù)對象初始化為數(shù)組的第一個元素忍些,然后將其傳入內(nèi)部遞歸閉包鲁猩,閉包中先判斷對象
__proto__
(原型,受chrome,safari,firefox支持)罢坝、prototype
(構(gòu)造函數(shù)的原型對象)廓握、constructor
三個屬性是否存在搅窿,把存在的屬性的值(是一個對象)依次放入數(shù)組,然后以此將存在的屬性值作為參數(shù)遞歸地傳入閉包中隙券。需要注意的是將存在的屬性放入數(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瀏覽器中運行戏售,利用控制臺的輸出啦辐,整理得到下面這張圖:
- 圖中0、1蜈项、3芹关、5都是函數(shù),2紧卒、4是對象侥衬。0就是我們定義的
baseFunc
函數(shù),它的原型對象是2跑芳,它作為一個對象有原型1轴总,對象1是一個函數(shù),但是我們未能獲得它的函數(shù)名博个,我們把它記為函數(shù)X怀樟,對象1的原型是對象4。 - 對象3就是構(gòu)造函數(shù)
Function
盆佣,對象5就是構(gòu)造函數(shù)Object
往堡。 - 圖中所有對象的原型鏈走到頂端都是對象4械荷,它就是被
ECMAScript
中所有對象繼承的Object對象。 - 圖中的函數(shù)都繼承自函數(shù)X(對象1)虑灰,因此我們基本可以推論說函數(shù)就是一個X類型吨瞎;
- 由于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
的屬性,所以沒有被普通對象繼承艰毒。