學事後端語言的同窗對繼承並不陌生,可是對JS繼承少量仍是有些困惑,不要試圖問我是若是知道的,其實javascript繼承主要是基於原型prototype
實現的。javascript
其實當你真正瞭解了原型鏈時候,再看js繼承,其實比OOP語言更靈活、更簡單一些。接下來咱們來看看原型鏈繼承吧:java
//父類 function Animal(){} //子類 function Dog(){} //繼承 Dog.prototype = new Animal();
其實,就是把子類的prototype
指向父類的實例,繼承就完成了,很簡單吧。這就是原型鏈繼承
上面只是一個簡單的繼承結果,並沒有實際意義,繼承的目的就是要共享父類的屬性和方法,接下來咱們一步一步來揭開這神祕的面紗git
/** * * 父類,帶屬性 * @constructor * @param name 名字 * @param type 動物分類 * @constructor */ function Animal(name,type) { this.name = name || 'your name'; this.type= type || 0; this.coatColor= ['white','block','yellow','brown']; //引用類型 //函數也是引用類型 this.speak = function () { console.log(this.name+' speaking .'); } } } /** * 爲父類新增一個方法 * @returns {boolean} */ Animal.prototype.say= function () { console.log('my name is '+this.name); }; /** * 子類 * @constructor */ function Dog(name) { this.name = name; this.foot= 4; } //實現繼承-原型鏈繼承 => (子類 -> 子類原型->父類) ;繼承 注意,繼承必需要寫在子類方法定義的前面 Dog.prototype = new Animal(); /** * 子類方法 * 爲子類新增一個方法(在繼承以後,不然會被覆蓋/異常) dog.run is not a function */ Dog.prototype.run = function () { console.log('The '+ this.name +' was runing.'); }; var dog = new Dog('taiSen'); console.log(dog.name); //dog --子類覆蓋父類的屬性 console.log(dog.type); // 0 --父類的屬性 console.log(dog.foot); //4 --子類本身的屬性 dog.say(); //my name is taiSen --繼承自父類的方法 dog.run(); //The taiSen was runing. --子類本身的方法
以上,看起來咱們好像已經完成了一個完整的繼承了。可是,原型鏈繼承有一個缺點
,就是屬性
若是是引用類型
的話,會共享
引用類型 ,接下來我個Animal增長引用類型屬性this.coatColor,測試下github
//測試下 var dog1= new Dog(); var dog2 = new Dog(); dog1.coatColor.push('blue'); console.log(dog1.coatColor); // [ 'white', 'block', 'yellow', 'brown', 'blue' ] console.log(dog2.coatColor); // [ 'white', 'block', 'yellow', 'brown', 'blue' ]
dog1,dog2 輸出的coatColor同樣,說明引用類型屬性會被
全部實例共享
--- 這就是原型鏈繼承的缺點,那麼咱們若是解決這個問題呢? 接下來咱們就要借用————
構造函數繼承
//子類 function Cat() { Animal.call(this) // 構造函數繼承(繼承屬性) } //測試下 var cat1= new Cat(); var cat2 = new Cat(); cat1.coatColor.push('red'); console.log(cat1.coatColor); // [ 'white', 'block', 'yellow', 'brown', 'red' ] console.log(cat2.coatColor); // [ 'white', 'block', 'yellow', 'brown']
從結果看,咱們就解決了引用類型被全部實例共享的問題了。後端
注意
:這裏跟原型鏈繼承
有個比較明顯的區別是並無使用prototype
繼承,而是在子類裏面執行父類的構造函數, 至關於把父類的代碼複製到子類裏面執行一遍,這樣作的另外一個好處就是能夠給父類傳參。
測試代碼:函數
/好比: function Pig(name) { Animal.call(this,name); } var pig1= new Animal('big Pig'); var pig2 = new Animal('small Pig'); console.log(pig1.name); // big Pig console.log(pig2.name); //small Pig
看起來是否是很像java,C#語言啊,以上構造函數解決了引用類型被全部實例共享的問題。測試
注意
: 正由於構造函數解決了解決了引用類型被全部實例共享的問題,致使了一個相對很矛盾的問題出現了,——————函數
也是 引用類型,函數也沒辦法共享了.也就是說,每一個實例裏面的函數,雖然功能同樣,可是卻不是同一個函數,就至關於咱們每實例化一個子類,就複製了一遍的函數代碼。
//父類新增this.speak函數 function Animal(name,type) { this.name = name || 'your name'; this.type= type || 0; this.coatColor= ['white','block','yellow','brown']; //引用類型 //函數也是引用類型 this.speak = function () { console.log(this.name+' speaking .'); } } //測試 console.log(pig1.speak===pig2.speak); // false
以上證實,父類的函數,在子類的實例下是不共享的。this
怎麼辦呢?,以上能夠看出原型鏈繼承 和 構造函數繼承 這兩種繼承方式的優缺點恰好是互相矛盾的,那麼咱們有沒有辦法魚和熊掌兼得呢? 答案是確定的————組合繼承prototype
// 父類 function Animal() { this.name = name || 'your name'; this.type= type || 0; this.coatColor= ['white','block','yellow','brown']; //引用類型 } // 父類函數 Animal.prototype.speak =function () { console.log(this.name+' speaking .'); } // 子類 function Chicken(){ Animal.call(this) // 構造函數繼承(繼承屬性) } // 繼承 Chicken.prototype = new Animal() // 原型鏈繼承(繼承方法)
繼承方式 | 核心代碼 | 優缺點 | 用法 |
---|---|---|---|
原型鏈繼承 | Dog.prototype = new Animal() | 實例的引用類型共享 | 繼承屬性 |
構造函數繼承 | 在子類Cat)裏執行 Animal.call(this) | 會獨享全部屬性,包括引用屬性(重點是函數) | 繼承方法 |
組合繼承 | 利用原型鏈繼承要共享的屬性,利用構造函數繼承要獨享的屬性 | 實現相對完美的繼承 | 結合上兩位 |
本文中的代碼見demo coding ,若是以爲對您有用,幫加個star,萬分感謝!code
今天就寫到這,講述了3種繼承方式,其實J繼承還有不少繼承方式。其餘留在下期再見咯。感受各位的收看,歡迎提問。