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對象。
問題
如果我有個(gè)對象,能不能把這個(gè)對象的原型鏈分析展現(xiàn)出來呢,特別地,函數(shù)作為對象自身的原型鏈?zhǔn)窃鯓拥哪??(我們知道函?shù)定義也可以通過調(diào)用構(gòu)造函數(shù)
Function實(shí)現(xiàn),這個(gè)構(gòu)造函數(shù)又是怎么和 最終的Object對象聯(lián)系的呢?)上面提到的方法
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ù)的思路:
首先將參數(shù)對象初始化為數(shù)組的第一個(gè)元素,然后將其傳入內(nèi)部遞歸閉包,閉包中先判斷對象
__proto__(原型,受chrome,safari,firefox支持)、prototype(構(gòu)造函數(shù)的原型對象)、constructor三個(gè)屬性是否存在,把存在的屬性的值(是一個(gè)對象)依次放入數(shù)組,然后以此將存在的屬性值作為參數(shù)遞歸地傳入閉包中。需要注意的是將存在的屬性放入數(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)的輸出,整理得到下面這張圖:

- 圖中0、1、3、5都是函數(shù),2、4是對象。0就是我們定義的
baseFunc函數(shù),它的原型對象是2,它作為一個(gè)對象有原型1,對象1是一個(gè)函數(shù),但是我們未能獲得它的函數(shù)名,我們把它記為函數(shù)X,對象1的原型是對象4。 - 對象3就是構(gòu)造函數(shù)
Function,對象5就是構(gòu)造函數(shù)Object。 - 圖中所有對象的原型鏈走到頂端都是對象4,它就是被
ECMAScript中所有對象繼承的Object對象。 - 圖中的函數(shù)都繼承自函數(shù)X(對象1),因此我們基本可以推論說函數(shù)就是一個(gè)X類型;
- 由于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的屬性,所以沒有被普通對象繼承。