??初學JavaScript,一直很好奇這個沒有類定義的語言如何實現(xiàn)了面向?qū)ο蟮某绦蛟O(shè)計模式,以及如何實現(xiàn)類屬性和方法的繼承。經(jīng)過我兩天的冥思苦想,終于有了自己的一些感悟。
首先我們明確幾個重要的概念:
1 什么是原型?
??根據(jù)JavaScript權(quán)威指南上的解釋:每一個JavaScript對象(null除外)都和另外一個對象關(guān)聯(lián)?!傲硪粋€”對象就是我們熟知的原型,每一個對象都從原型繼承屬性。沒有原型的對象不多,Object.prototype就是其中之一。它不繼承任何屬性。其他原型對象都是普通對象,普通對象都具有原型。所有內(nèi)置構(gòu)造函數(shù)(以及大部分自定義的構(gòu)造函數(shù))都具有一個繼承自O(shè)bject.prototype的原型。
2 JavaScript 中Function(函數(shù))和Object(對象)的關(guān)系?
??JavaScript最開始定義一個對象叫做Object.prototype,本質(zhì)上就是一組無序key-value存儲({}),之后再此基礎(chǔ)上,研發(fā)出可以保存一段“指令”并“生產(chǎn)產(chǎn)品”的原型,叫作函數(shù),起名Function.prototype,本質(zhì)上就是[Function:Empty](空函數(shù))。
??為了規(guī)?;a(chǎn)生對象,JS在函數(shù)的基礎(chǔ)上構(gòu)造出了兩個構(gòu)造器:生產(chǎn)函數(shù)對象的構(gòu)造器叫做Function,生產(chǎn)key-value存儲對象的構(gòu)造器叫做Object。
??JavaScript在每個對象上打了個標簽__proto__,以標明這個對象是根據(jù)哪個原型生產(chǎn)的。為原型打了個標簽constructor,標明哪個構(gòu)造器可以依照這個原型生產(chǎn)對象。為構(gòu)造器打了標簽prototype,標明這個構(gòu)造器可以從哪個原型生產(chǎn)對象。
3 什么是原型鏈?
??原型鏈是一種機制,指的是JavaScript的每一個對象包括原型對象都有一個[[proto]](通過__proto__訪問)屬性指向創(chuàng)建它的函數(shù)對象的原型對象,即函數(shù)的prototype屬性。原型對象又可以通過[[proto]]屬性訪問它的上層原型對象,一層一層往上繼承屬性和方法,這就是原型鏈。原型鏈的作用就是用來實現(xiàn)對象的繼承。
原型鏈示意圖:

默認JavaScript中的構(gòu)造函數(shù)當做類,new出來的對象稱為實例,原型對象簡稱原型,具體實現(xiàn)上代碼:
首先我們構(gòu)造父類SuperClass,并創(chuàng)建它的原型:
function SuperClass() {
if(!(this instanceof SuperClass))
return new SuperClass();
else{
this.id1 = '1';
this.color1 = ['red'];
}
}
SuperClass.prototype={
id2 : '2',
color2 : ['yellow']
};
然后構(gòu)造一個空的子類:
function SubClass() {
if(!(this instanceof SubClass))
return new SubClass();
else{}
}
定義一個打印對象所有屬性和方法的函數(shù)以便調(diào)試:
function ConsoleDetail(obj) {
for(var i in obj){
console.log(i+':'+obj[i]);
}
}
這里我對構(gòu)造函數(shù)本身的屬性方法和創(chuàng)建出來對象的屬性方法有點迷惑,于是測試:
console.log('-----------------------------------');
SuperClass.name = 'wenjun';
ConsoleDetail(SuperClass);
console.log('-----------------------------------');
var obj = new SuperClass();
ConsoleDetail(obj);
Chrome控制臺輸出:

可以看到實例化的對象構(gòu)造屬性方法和原型屬性方法都符合預期。但是SubClass對象上明明定義了name屬性,卻什么也沒打印出來,出了什么錯?
原來Function.name是一個自帶屬性,它的值就是我們定義Function時取得名字(字符串類型),對于匿名函數(shù),它的值為“anonymous”。詳情可以閱讀文檔。
既然name不行,換個age試試:
console.log('-----------------------------------');
SuperClass.age = '20';
ConsoleDetail(SuperClass);
console.log('-----------------------------------');
var obj = new SuperClass();
ConsoleDetail(obj);
結(jié)果:

構(gòu)造函數(shù)本身也是一個對象,它自身可以掛載屬性和方法,然而這和使用它構(gòu)造出來的實例的屬性和方法(通過關(guān)鍵字<this.XXX>定義)沒有任何關(guān)系!
不糾結(jié)這個了,正式開始繼承:
console.log('-----------------------------------');
SubClass.prototype = new SuperClass();
ConsoleDetail(SubClass);
console.log('-----------------------------------');
var obj = SubClass();
ConsoleDetail(obj);
結(jié)果:

子類原型賦值為父類實例,對子類本身沒有任何影響,子類上依舊沒有任何屬性,而子類構(gòu)造的實例卻可以繼承子類構(gòu)造函數(shù)和父類構(gòu)造函數(shù)的屬性和方法(通過關(guān)鍵字<this.XXX>定義)及父類原型上的屬性和方法。
為了對比實例調(diào)用父類繼承屬性和方法的效果,我多構(gòu)建一個obj2同樣繼承SubClass:
var obj2 = SubClass();
先更改SuperClass構(gòu)造函數(shù)中的id1和color1:
obj.id1 = '11';
obj.color1.push('black');
ConsoleDetail(obj2);
結(jié)果:

發(fā)現(xiàn)id1沒變,而color1變了。
繼續(xù)更改SuperClass原型上的id2和color2:
obj.id2 = '22';
obj.color2.push('white');
ConsoleDetail(obj2);
結(jié)果:

發(fā)現(xiàn)id2沒變,而color2變了。
子類實例繼承父類構(gòu)造函數(shù)和原型對象的屬性及方法,只要是值類型的都是賦值(深拷貝),對象類型都是引用(淺拷貝)!
接著放棄之前的修改,單純嘗試重新定義屬性或方法:
obj.color1 = function () {
console.log("redefine color1.");
};
ConsoleDetail(obj);
console.log('-----------------------------------');
ConsoleDetail(obj2);
結(jié)果:

這里的color1是Function對象,如果按照之前的邏輯,對象類型都是引用,為什么這里obj2沒有任何變化?
仔細思考了很久,我能給出的最合理解釋是:obj.color1 = function(){...}實際上是在實例上掛載了與通過原型鏈繼承的屬性或方法同名的屬性或方法,導致新定義的屬性或方法覆蓋了obj1原型鏈上的同名屬性或方法,只是查詢(從實例本身->實例原型->原型的原型->...,瀏覽器似乎也是按照這個順序打印結(jié)果的)該屬性會忽略使用原型上的同名屬性或方法,但實際上該操作并沒有改變原型對于該屬性或方法的定義!所以obj2繼承過來的colo1沒有任何改變,但是瀏覽器打印obj的順序卻有了變化,這又剛好印證了我的解釋。
??這就是我對于原型鏈在類式繼承模式下的理解,初學JavaScript,水平有限,里面一些解釋還只是自圓其說,沒有找到資料確認,但是我會好好加油的!也希望大家指出我的不足,給我提供一些學習JavaScript的意見和建議,之后我還會分享其他工廠模式下的繼承鏈分析,敬請期待!