javascript 中實現繼承的六種方式

javascript 中對於繼承的描述:javascript

            許多面向對象語言都支持兩種繼承的方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。在 javascript 中因爲函數沒有簽名也就沒法實現接口繼承,而只支持實現繼承,並且實現繼承主要經過原型鏈來實現的。java

            先引述下官方文檔對於原型鏈的描述:其基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。要理解這個概念要先弄清楚構造函數,原型,和實例的關係:每一個構造函數(只要是函數)都有一個 prototype 屬性該屬性指向一個對象(這個對象就是構造函數的原型對象);原型對象(只要是對象)中都有一個 constructor 屬性該屬性指向一個構造函數;而實例中都包含一個指向原型對象的內部指針 `Prototype`。說白了就是原型鏈的構建是經過將一個類型的實例賦值給另外一個構造函數的原型實現的。這樣子類型就能夠訪問定義在超類型上的全部屬性和方法了。每一個對象都有本身的原型對象,以原型對象爲模板從原型對象中繼承屬性和方法,原型對象也能夠有本身的原型並從中繼承屬性和方法,一層一層,以此類推,這種關係被稱爲原型鏈它解釋了爲什麼一個對象會擁有定義在其餘對象上的屬性和方法。
app


javascript 中實現繼承的六種方式:ide

            一、原型鏈函數

            二、借用構造函數
this

            三、組合繼承(組合使用原型鏈和借用構造函數)
spa

            四、原型式繼承
prototype

            五、寄生式繼承
指針

            六、寄生組合式繼承(組合使用組合繼承和寄生式繼承)orm

一、原型鏈

具體示例:
// 實現原型鏈的一種基本模式
function SuperType(){
           this.property = true;
}
SuperType.prototype.getSuperValue = function(){
           return this.property;
};
function SubType(){
           this.subproperty = false;
}

// 繼承,用 SuperType 類型的一個實例來重寫 SubType 類型的原型對象
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
           return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());     // true
PS:SubType 繼承了 SuperType,而繼承是經過建立 SuperType 的實例,並將該實例賦值給 SubType 的原型實現的。實現的本質是重寫子類型的原型對象,代之以一個新類型的實例。子類型的新原型對象中有一個內部屬性 `Prototype` 指向了 SuperType 的原型,還有一個從 SuperType 原型中繼承過來的屬性 constructor 指向了 SuperType 構造函數。最終的原型鏈是這樣的:instance 指向 SubType 的原型,SubType 的原型又指向 SuperType 的原型,SuperType 的原型又指向 Object 的原型(全部函數的默認原型都是 Object 的實例,所以默認原型都會包含一個內部指針,指向 Object.prototype)。
原型鏈的缺點:
           一、在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例。因而,原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性,而且會被全部的實例共享。這樣理解:在超類型構造函數中定義的引用類型值的實例屬性,會在子類型原型上變成原型屬性被全部子類型實例所共享。
           二、在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。

二、借用構造函數(也稱僞造對象或經典繼承)

具體示例:
// 在子類型構造函數的內部調用超類型構造函數;使用 apply() 或 call() 方法將父對象的構造函數綁定在子對象上
function SuperType(){
           // 定義引用類型值屬性
           this.colors = ["red","green","blue"];
}
function SubType(){
           // 繼承 SuperType,在這裏還能夠給超類型構造函數傳參
           SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("purple");
alert(instance1.colors);     // "red,green,blue,purple"

var instance2 = new SubType();
alert(instance2.colors);     // "red,green,blue"
PS:經過使用 apply() 或 call() 方法,咱們其實是在將要建立的 SubType 實例的環境下調用了 SuperType 構造函數。這樣一來,就會在新 SubType 對象上執行 SuperType() 函數中定義的全部對象初始化代碼。結果 SubType 的每一個實例就都會具備本身的 colors 屬性的副本了。
借用構造函數的優勢是解決了原型鏈實現繼承存在的兩個問題;借用構造函數的缺點是方法都在構造函數中定義,所以函數複用就沒法實現了。並且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果全部類型都只能使用構造函數模式。

三、組合繼承(也稱僞經典繼承)

具體示例:
// 將原型鏈和借用構造函數的技術組合到一塊。使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有本身的屬性。
function SuperType(name){
           this.name = name;
           this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
           alert(this.name);
};
function SubType(name,age){
           // 借用構造函數方式繼承屬性
           SuperType.call(this,name);
           this.age = age;
}
// 原型鏈方式繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
           alert(this.age);
};
var instance1 = new SubType("luochen",22);
instance1.colors.push("purple");
alert(instance1.colors);      // "red,green,blue,purple"
instance1.sayName();
instance1.sayAge();

var instance2 = new SubType("tom",34);
alert(instance2.colors);      // "red,green,blue"
instance2.sayName();
instance2.sayAge();
PS:組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,成爲 javascript 中最經常使用的繼承模式。並且,使用 instanceof 操做符和 isPrototype() 方法也可以用於識別基於組合繼承建立的對象。可是,它也有本身的不足。最大的問題是不管在什麼狀況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。

四、原型式繼承

具體示例:
// 藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
一、自定義一個函數來實現原型式繼承
function object(o){
           function F(){}
           F.prototype = o;
           return new F();
}
PS:在 object() 函數內部,先建立一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回這個臨時類型的一個新實例。實質上,object() 對傳入其中的對象執行了一次淺複製。
二、使用 Object.create() 方法實現原型式繼承。這個方法接收兩個參數:一是用做新對象原型的對象和一個爲新對象定義額外屬性的對象。在傳入一個參數的狀況下,此方法與 object() 方法做用一致。
在傳入第二個參數的狀況下,指定的任何屬性都會覆蓋原型對象上的同名屬性。
var person = {
           name: "luochen",
           colors: ["red","green","blue"]
};
var anotherPerson1 = Object.create(person,{
           name: {
                   value: "tom"
           }
});
var anotherPerson2 = Object.create(person,{
           name: {
                   value: "jerry"
           }
});
anotherPerson1.colors.push("purple");
alert(anotherPerson1.name);     // "tom"
alert(anotherPerson2.name);     // "jerry"
alert(anotherPerson1.colors);    // "red,green,blue,purple"
alert(anotherPerson2.colors);    // "red,green,bule,purple";
PS:只是想讓一個對象與另外一個對象相似的狀況下,原型式繼承是徹底能夠勝任的。可是缺點是:包含引用類型值的屬性始終都會共享相應的值。

五、寄生式繼承

具體示例:
// 建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後返回這個對象
function createPerson(original){
           var clone = Object.create(original);   // 經過 Object.create() 函數建立一個新對象
           clone.sayGood = function(){              // 加強這個對象
                       alert("hello world!!!");
           };
           return clone;                                      // 返回這個對象
}
PS:在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。此模式的缺點是作不到函數複用。

六、寄生組合式繼承

具體示例:// 經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型function SuperType(name){            this.name = name;            this.colors = ["red","green","blue"];}SuperType.prototype.sayName = function(){            alert(this.name);};function SubType(name,age){            SuperType.call(this,name);            this.age = age;}// 建立超類型原型的一個副本var anotherPrototype = Object.create(SuperType.prototype);// 重設因重寫原型而失去的默認的 constructor 屬性anotherPrototype.constructor = SubType;// 將新建立的對象賦值給子類型的原型SubType.prototype = anotherPrototype;SubType.prototype.sayAge = function(){            alert(this.age);};var instance1 = new SubType("luochen",22);instance1.colors.push("purple");alert(instance1.colors);      // "red,green,blue,purple"instance1.sayName();instance1.sayAge();var instance2 = new SubType("tom",34);alert(instance2.colors);      // "red,green,blue"instance2.sayName();instance2.sayAge();PS:這個例子的高效率體如今它只調用一次 SuperType 構造函數,而且所以避免了在 SubType.prototype 上面建立沒必要要,多餘的屬性。與此同時,原型鏈還能保持不變;所以還可以正常使用 instance 操做符和 isPrototype() 方法。
相關文章
相關標籤/搜索