今天記錄一下學習javascript的繼承。javascript
繼承基本上是基於「類」來講的,而javascript中並不存在真正的類,因此就出現了各類模擬「類」的行爲,而後就冠冕堂皇的使用起了類的概念。這裏不談「類」的概念對js學習形成了多大的困難,只說一下普通意義上繼承的6種方式。java
一、原型鏈繼承app
這是js開發人員一開始接觸js,最容易學會的,因此說他就是最簡單的繼承的方法:函數
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(this.arr) } function Subtype(val, name) {} Subtype.prototype = new Super(); var b1 = new Subtype(12); console.log(b1.val) // undefined var b2 = new Subtype(); b1.fn(); // [1] b1.arr.push(2); console.log(b2.arr); // [1,2]
很容易的構造了一個子類Subtype,繼承了父類的全部屬性和共享方法。學習
可是缺點也很明顯:this
一、子類沒法傳入初始參數,對於這裏而言,就是Supertype沒法爲內部屬性val傳遞一個合理的值;b1實例化的時候,爲val傳入參數12,可是b1.val卻取不到該值。prototype
二、若是屬性值不是一個標量基本類型,是一個對象,則會出現若是子類實例對該對象進行修改,因爲對象屬性所存儲的僅僅是一個引用,而不是其真實值,因此會致使修改一個實例的對象屬性的值,致使另外一個實例的該對象引用的值跟着變化。這裏就是對實例b1的屬性arr添加值操做的話,實例2引用的arr會同事變化,由於本質上他們所指向的是一個值。對象
爲了修改上述問題,則引伸出了第二種繼承方式:blog
二、借用構造函數繼承
function Super(val) { this.val = val; this.arr = [1]; this.fn = function () { console.log('prototype'); } } function Subtype() { Super.apply(this, arguments); } var sub1 = new Subtype('s', 'zhongg'); var sub2 = new Subtype('sdsdds', 'fsdajfsldaf'); sub1.arr.push(5); console.log(sub2.arr);
很明顯,修改了上述原型鏈繼承方式的兩個存在問題,可是卻又產生了一個新的問題,函數不能公用的問題,因爲子類是直接調用的父類函數,這就與原型prototype沒有任何關係了,從而會致使定義在原型prototype上的任何函數都不起做用,這麼一來,全部須要用到的函數就必須定義在父類函數內部。當子類比較多,實例化不少的時候,這就會形成每個實例化都實例化了相同的函數,會佔用大量內存。
那麼必然的,既然目前的解決方案還不完美,確定會有更完美的解決方案:
三、組合繼承
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(this.val + 'prototype'); } function Subtype(val, name) { Super.apply(this, arguments); } Subtype.prototype = new Super(); var sub1 = new Subtype('值', '鍵值對'); var sub2 = new Subtype('值2', '鍵值對2'); sub1.fn();
組合繼承,顧名思義,就是組合了其餘的繼承方式而成,這裏其實就是組合了原型鏈繼承和借用構造函數繼承。
已經基本上完美了,綜合了前兩種繼承方式的全部優勢。既然說是基本上完美,那確定仍是有點瑕疵的,這裏的瑕疵就是調用了兩次父類函數,一次直接調用,一次new調用,致使生成了兩份的實例屬性,對於內存而言也是一種浪費。
到ES5時候,出現了一種能夠真正完美解決繼承全部缺點的繼承方式:
四、寄生組合繼承
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(10 + this.val); } function Subtype(val, name) { Super.apply(this, arguments); this.name = name; } Subtype.prototype = Object.create(Super.prototype); Subtype.prototype.fun = function () { console.log("fnu:" + this.name); } Subtype.prototype.constructor = Subtype; var sub1 = new Subtype('zhi', '名字是什麼比較好呢?'); var sub2 = new Subtype('again', 'nameto'); console.log(sub1.constructor) sub1.fun(); sub1.fn(); sub1.arr.push(3);
利用的是ES5當中的Object.create(...),《你不知道的javascript》中對Object.create(..)的解釋:Object.create(..)會建立一個新對象並把他關聯到咱們指定的對象。這裏就是建立一個新的對象 Subtype.prototype 並把他關聯到 Super.prototype,這裏也會產生另一個小問題,須要進行修補。就是Object.create(..)建立的對象沒有constructor屬性,若是須要constructor屬性的話,那麼在Subtype.prototype建立以後,須要手動修補,Subtype.prototype.constructor = Subtype。這麼一來,繼承上產生的各類問題都真正完美了。
因爲Object.create(...)是在ES5中定義的,因此這個方案提出以前,其實利用的是Object.create(...)的polyfill的方案,相似於:
if (!Object.create) { Object.create = function (o) { function F (){}; F.prototype = o; return new F(); } }
可是因爲該方案出現的時間比較晚,雖然此方案是真正完美的方案,可是並無上述的組合繼承的方案使用普遍。事實上,組合繼承的方案也基本上可以保證咱們的平常開發了。
另外還有兩種繼承方案,但是已經不太像繼承了:
五、原型式繼承
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(10 + this.val); } var sup = new Super('父類'); var sub = Object.create(sup); sub.func1= function(){};
// 後面就是爲子類sub添加各類須要的方法或者屬性
仔細比較一下,其實原型式繼承和原型鏈繼承仍是比較類似的,區別在於原型鏈繼承的 Subtype.prototype = new Super();而原型式繼承爲 var sup = new Super('父類'); var sub = Object.create(sup);應該可以看出區別所在了。
六、寄生繼承
最後一種繼承方式真心的不是很想說,沒什麼可說的,其實就是把原型式繼承的子類繼承部分封裝成函數來實現而已。
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(10 + this.val); } function Subtype(o){ var clone = Object.create(o); clone.func1= function(){}; // 添加各類屬性 } var sub = Subtype(new Super());
在平常開發中,通常使用第三種組合繼承的方式,若是想要求更高一點的話,可使用第四種寄生組合繼承的方式。