JavaScript之物件繼承

laixiangran發表於2018-05-09

原型鏈繼承

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

function SubType(){
    this.subproperty = false;
}

// 繼承了 SuperType
SubType.prototype = new SuperType();

var subType = new SubType();
console.log(subType.getSuperValue());   // 繼承了 SuperType 的 getSuperValue 方法,列印 true

缺點

  1. 如果 SuperType 存在一個引用型別的屬性,而 SubType 的原型物件變為 SuperType 的一個例項,這樣每個 SubType 的例項都會共用這個引用型別的屬性,不同的 SubType 例項對該屬性的操作都將會在其它 SubType 例項中體現出來,這跟每個例項擁有自己的屬性是違背的。
function SuperType(){
    this.colors = ["red", "blue", "green"];
}

function SubType(){            
}

// 繼承了 SuperType
SubType.prototype = new SuperType();

var subType1 = new SubType();
subType1.colors.push("black"); // subType1 修改 colors
console.log(subType1.colors);    // "red,blue,green,black"

var subType2 = new SubType();
console.log(subType2.colors);    // "red,blue,green,black",subType2 的 colors 值為 subType1 修改之後的值
  1. 在建立子型別(SubType)的例項時,不能向超型別(SuperType)的建構函式中傳遞引數。實際上,應該說是沒有辦法在不影響所有物件例項(原因如缺點1)的情況下,給超型別的建構函式傳遞引數。

借用建構函式繼承

function SuperType(){
    this.colors = ["red", "blue", "green"];
}

function SubType(){  
    // 繼承了 SuperType
    SuperType.call(this); // 通過 apply() 或 call() 執行 SuperType 的建構函式
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    // "red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors);    // "red,blue,green"

該方法解決了 原型鏈繼承 的引用型屬性共享的問題。

還有可以在子型別建構函式中向超型別建構函式傳遞引數,如下例子:

function SuperType(name){
    this.name = name;
}

function SubType(){  
    // 繼承 SuperType,並傳遞引數
    SuperType.call(this, "Nicholas");
    
    // 例項屬性
    this.age = 29;
}

var subType = new SubType();
console.log(subType.name);   //"Nicholas";
console.log(subType.age);    //29

缺點

  1. 方法都在建構函式中定義,因此函式複用就無從談起了。而且,在超型別的原型中定義的方法,對子型別而言也是不可見的,結果所有型別都只能使用建構函式模式。

原型鏈/建構函式組合繼承

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
};

function SubType(name, age){
    // 執行 SuperType 的建構函式,繼承 SuperType 的屬性
    SuperType.call(this, name);
    
    this.age = age;
}

// 將 SuperType 的例項賦給 SubType 的原型物件,這樣 SubType 可繼承 SuperType 原型中的方法
SubType.prototype = new SuperType();

SubType.prototype.sayAge = function(){
    console.log(this.age);
};

var  subType1 = new SubType("Nicholas", 29);
subType1.colors.push("black");
console.log(subType1.colors);  // "red,blue,green,black"
subType1.sayName();      // "Nicholas";
subType1.sayAge();       // 29


var subType2 = new SubType("Greg", 27);
console.log(subType2.colors);  // "red,blue,green"
subType2.sayName();      // "Greg";
subType2.sayAge();       // 27

缺點

  1. 無論什麼情況,都會呼叫兩次超型別建構函式,一次是在建立子型別原型的時候,一次是在執行子型別建構函式的時候。這樣一個 SubType 例項會包含兩組 SuperType 的屬性,一組在 SubType 例項上,一組在 SubType 原型中。

原型式繼承

該方法基於已有的物件建立新物件,同時還不必因此建立自定義型別。

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = Object.create(person); // 使用 person 作為新物件(anotherPerson)的原型
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.log(person.friends);   // "Shelby,Court,Van,Rob,Barbie"

寄生式繼承

該方法建立一個僅用於封裝繼承過程的函式,該函式在內部以某種方式來增強物件,最後再像真地是它做了所有工作一樣返回物件。

function creatAnother(original) {
    var clone = Object.create(original); // 建立一個新物件
    clone.sayHi = function() { // 以某種方式來增強這個物件
        console.log("hi");
    }
    return clone; // 返回該物件
}

// 使用示例
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = creatAnother(person);
anotherPerson.sayHi(); // "hi"

寄生/原型鏈/建構函式組合繼承

該方法解決原型鏈/建構函式組合繼承呼叫兩次超型別建構函式的問題。

通過借用建構函式來繼承屬性,通過原型鏈的混成形式來繼承方法。其背後的基本思路是:不必為了指定子型別的原型而呼叫超型別的建構函式,我們所需要的無非就是超型別原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超型別的原型,然後再將結果指定給子型別的原型。

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);   // 根據超型別原型建立新物件
    prototype.constructor = subType;               // 將新物件的 constructor 設定為子型別
    subType.prototype = prototype;                 // 將新物件賦給子型別的原型
}
                        
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
};

function SubType(name, age){  
    SuperType.call(this, name);
    
    this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function(){
    console.log(this.age);
};

var subType1 = new SubType("Nicholas", 29);
subType1.colors.push("black");
console.log(subType1.colors);  // "red,blue,green,black"
subType1.sayName();      // "Nicholas";
subType1.sayAge();       // 29


var subType2 = new SubType("Greg", 27);
alert(subType2.colors);  // "red,blue,green"
subType2.sayName();      // "Greg";
subType2.sayAge();       // 27

參考資料:《JavaScript高階程式設計(第3版)》第6.3節 繼承

相關文章