JavaScript繼承模式

原文地址:JavaScript實現繼承javascript

衆所周知,JavaScript 這門語言在 ES6 出來以前是沒有類(class)這一律唸的,因此 JavaScript 中的類都是經過原型鏈來實現的。一樣,使用 JavaScript 也能實現面向對象的實現繼承。如下是《高程》(第三版)的讀書筆記。java

原型鏈

經過原型鏈實現繼承很容易理解,也很簡單。將子類的原型指向父類的實例便可。寫個簡單的例子:git

// 父類
function SuperType() {
    this.property = "super";
}
// 在父類的原型上定義方法
SuperType.prototype.getSuperVal = function () {
    console.log(this.property);
};

// 子類
function SubType() {
    this.property = "sub";
}
// 子類繼承父類
SubType.prototype = new SuperType();

var instance = new SubType();
console.log(instance.getSuperVal()); // "sub"

對於子類來說,其原型的指向應該是這樣的:SubType -> new SuperType() -> SuperType.prototype -> Object.prototype -> nullgithub

注意:函數

  • 若是想要給子類添加原型上的方法,須要在子類繼承了父類後添加,不然會被父類實例所覆蓋。
  • 也不要用對象字面量的方式給子類原型添加新方法,這會使得以前的繼承失效。

原型鏈的問題:this

  • 父類的實例屬性成爲了子類的原型屬性,即子類的實例共享了該父類實例的屬性,若是該屬性是引用類型,則子類的實例對該屬性的修改會反映在全部的子類實例上。
  • 在建立子類實例時,不能向父類的構造函數傳遞參數。實際上,應該說是沒有辦法在不影響全部對象實例的狀況下,給父類的構造函數傳遞參數。

借用構造函數

這個方法是爲了解決原型鏈方式帶來的問題,使用十分巧妙,利用了 call 方法。代碼實現:prototype

// 父類
function SuperType() {
    this.users = ["Jack", "Tom"];
}
// 子類
function SubType() {
    // 繼承
    SuperType.call(this);
}

var instance1 = new SubType();
var instance2 = new SubType();

instance1.users.pop(); // "Tom"
console.log(instance2.users); // ["Jack", "Tom"]

經過借用構造函數解決了共享原型屬性致使的問題。同時也能夠經過 call 方法給父類傳遞參數。設計

借用構造函數的問題:code

  • 方法都須要在構造函數(父類或子類)中定義,沒法達到函數複用的功能。

組合繼承

組合繼承有時也叫僞經典繼承,該繼承模式將原型鏈和借用構造函數的技術結合在一塊兒實現。示例代碼:對象

// 父類
function SuperType(company) {
    this.company = company;
    this.staffs = ["Jack", "Tom"];
}
// 父類方法
SuperType.prototype.getCompany = function () {
    console.log(this.company);
};

// 子類
function SubType(company, product) {
    // 繼承屬性
    SuperType.call(this, company);

    this.product = product;
}
// 繼承方法
SubType.prototype = new SuperType();
// 指向正確的constructor
SubType.prototype.constructor = SubType;
SubType.prototype.getProduct = function () {
    console.log(this.product);
};

// SubType實例
var instance1 = new SubType("A", "tellphone");
instance1.getCompany(); // "A"
instance1.getProduct(); // "tellphone"
instance1.staffs.push("Amy"); // ["Jack", "Tom", "Amy"]

var instance2 = new SubType("B", "toy");
instance2.getCompany(); // "B"
instance2.getProduct(); // "toy"
console.log(instance2.staffs); // ["Jack", "Tom"]

從代碼的例子能夠觀察到,組合繼承模式可讓子類的多個實例既能擁有本身的屬性,又能使用相同的方法,融合了原型鏈和借用構造函數的優勢。

原型式繼承

原型式繼承是藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。使用以下函數實現:

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

object 函數對傳入的對象實現了淺複製。因此對全部由其建立的實例都共享了 obj 對象中的引用屬性。

寄生式繼承

寄生式繼承模式和原型式繼承模式很類似,建立了一個僅用於封裝繼承過程的函數,在函數內部加強對象的功能:

function createAnother(obj) {
    var clone = object(obj);
    clone.saySomething = function () {
        alert("Hello world!");
    };

    return clone;
}

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

經過 createAnother 函數,對對象的功能進行加強,然而這種方式也沒有達到函數複用的效果,這一點和構造函數模式同樣。

寄生組合式繼承

經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。寄生組合模式使用寄生模式來實現對父類原型的繼承,再將結果指定給子類的原型。其基本模式以下:

function inheritPrototype(subType, superType) {
    // 返回父類原型副本並賦值給子類原型
    subType.prototype = object(superType.prototype);
    subType.prototype.constructor = subType;
}

再來看一個例子:

// 父類
function SuperType(name) {
    this.name = name;
}
// 在父類的原型上定義方法
SuperType.prototype.getName = function () {
    console.log(this.name);
};

// 子類
function SubType(name, age) {
    SuperType.call(this, name);

    this.age = age;
}

// 寄生組合繼承
inheritPrototype(SubType, SuperType);

// 添加子類方法
SubType.prototype.getAge = function () {
    console.log(this.age);
};

和組合繼承模式相比,寄生組合式繼承模式只調用了一次 SuperType 構造函數,也避免了在 SubType.prototype 上建立多餘的屬性。開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式。

參考書籍

《JavaScript高級程序設計》(第三版)

相關文章
相關標籤/搜索