《JavaScript高級程序設計(第3版)》第6章 面向對象的程序設計總結二

6.3、繼承

ES只支持實現繼承,而且其實現繼承主要是依靠原型鏈來實現的。

6.3.1、原型鏈

利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。

注意:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。

原型鏈基本概念:讓原型對象等於另一個類型的實例,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含着一個指向另一個構造函數的指針。

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

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

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

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

SubType.prototype.getSubValue = function() {
    return this.subproperty;
};

var instance = new SubType();
alert(instance.getSubValue());    // true
alert(instance.getSuperValue());    // true

上面代碼的圖解如下所示:

調用 instance.getSuperValue() 會經歷三個搜索步驟:1)搜索實例;2)搜索 SubType.prototype;3)搜索 SuperType.prototype,最後一步纔會找到該方法。

1、別忘記默認的原型

所有類型默認都繼承了 Object,而這個繼承也是通過原型鏈實現的(所有函數的默認類型都是 Object 的實例)。具體如下圖所示:

2、確定原型和實例的關係

可以通過兩種方式來確定原型和實例之間的關係:

第一種:使用 instanceof 操作符,

// 由於原型鏈的關係,可以說 instance 是
//  Object、SuperType、SubType中任何一個類型的實例
alert(instance instanceof Object);  // true
alert(instance instanceof SuperType);   // true
alert(instance instanceof SubType); // true

第二種:使用 isPrototypeOf() 方法,只要是原型鏈中出現過的原型,都可以說是該原型鏈所派生的實例的原型,因此 isPrototypeOf() 方法也會返回 true。

alert(Object.prototype.isPrototypeOf(instance));    // true
alert(SuperType.prototype.isPrototypeOf(instance)); // true
alert(SubType.prototype.isPrototypeOf(instance));   // true

3、慎重地定義方法

注意:給原型添加方法的代碼一定要放在替換原型的語句之後。

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

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

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

// 添加新方法
SubType.prototype.getSubValue = function() {
    return this.subprototype;
};

// 重寫超類型中的方法
// 會屏蔽原來的那個方法
SubType.prototype.getSuperValue = function() {
    return false;
};

var instance = new SubType();
alert(instance.getSubValue());    // false
alert(instance.getSuperValue());  // false

注意:在通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。

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

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

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

// 使用字面量添加新方法,會導致上一行代碼無效
SubType.prototype = {
    getSubValue : function() {
        return this.subprototype;
    },

    someOtherMethod : function() {
        return false;
    }
};

var instance = new SubType();
alert(instance.getSuperValue()); // is not a function

4、原型鏈的問題

問題一:來自包含引用類型值的原型。

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

function SubType() {
}

// 繼承了SuperType
// 繼承之後相當於 SubType.prototype.colors = ['red','blue','green'];
SubType.prototype = new 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,black"

var superType = new SuperType();
superType.colors.push('aa');
alert("superType colors are : " + superType.colors);    // "red,blue,green,aa"

問題二:在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。

6.3.2、借用構造函數

爲解決原型中包含引用類型值所帶來的問題,可以使用 借用構造函數 的技術來解決。即在子類型構造函數的內部調用超類型構造函數。

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

function SubType() {
    // 繼承了 SuperType
    SuperType.call(this);  //“借調”超類型的構造函數
}

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"

1、傳遞參數

借用構造函數:可以在子類型構造函數中向超類型構造函數傳遞參數。

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

function SubType() {
    // 繼承了 SuperType,同時還傳遞了參數
    // 調用超類型構造函數
    SuperType.call(this, 'Nicholas');

    // 相同的屬性名會覆蓋超類的同名屬性
    // this.name = 'Greg';

    // 實例屬性
    this.age = 29;
}

var instance = new SubType();
alert(instance.name);   // 'Nicholas'
alert(instance.age);    // 29

2、借用構造函數的問題

無法避免構造函數模式中存在的問題——方法都在構造函數中定義,函數無法複用。

6.3.3、組合繼承

組合繼承(又稱爲 僞經典繼承):將原型鏈和借用構造函數的技術組合到一塊。使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green', 'blue'];
}
SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    // 繼承超類 SuperType
    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('Nicholas', 29);
instance1.colors.push('black');
alert(instance1.colors);    // "red,green,blue,black"
instance1.sayName();        // 'Nicholas'
instance1.sayAge();         // 29

var instance2 = new SubType('Greg', 30);
alert(instance2.colors);    // "red,green,blue"
instance2.sayName();        // 'Greg'
instance2.sayAge();         // 30

組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優點,成爲JavaScript中最常用的繼承模式。

6.3.4、原型式繼承

藉助原型可以基於已用的對象創建新對象,同時還不必因此創建自定義類型。

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

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

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');  // "Shelby,Court,Van,Rob"
alert(anotherPerson.name);          // 'Greg'

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(yetAnotherPerson.name);   // 'Nicholas'

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

ES5通過新增 Object.create() 方法規範了原型式繼承。這個方法接收兩個參數:一個用作新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象。在傳入一個參數的情況下,Object.create() 與 object() 方法的行爲相同。

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

var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = Object.create(person);
alert(yetAnotherPerson.name);   // 'Nicholas'
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');

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

Object.create() 方法的第二個參數與 Object.defineProperties() 方法的第二個參數格式相同:每個屬性都是通過自己的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性

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

var anotherPerson = Object.create(person, {
    name: {
        value: 'Greg'
    }
});

alert(anotherPerson.name);  // 'Greg'

如果只想讓一個對象與另一個對象保持類似的情況下,原型繼承是完全可以勝任的。包含引用類型值的屬性始終都會共享相應的值,就像使用原型模式一樣。

6.3.5、寄生式繼承

寄生式繼承:即創建一個僅用於封裝過程的函數,該函數在內部以某種方式來增強對象,最後再像真地是它做了所有工作一樣返回對象。

function createAnother(original) {
    // 通過調用函數創建一個新對象
    var clone = Object(original);
    // 以某種方式來增強這個對象
    clone.sayHi = function() {
        alert('Hi');
    };

    return clone;   // 返回這個對象
}

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

var anotherPerson = createAnother(person);
anotherPerson.sayHi();  // 'Hi'

6.3.6、寄生組合式繼承

組合繼承最大的問題就是無論在什麼情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
// SuperType的原型
SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    // 第二次調用 SuperType()
    SuperType.call(this, name);

    this.age = age;
}

// 第一次調用 SuperType()
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    alert(this.age);
};

所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。其背後的基本思路是:不必爲了指定類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。

function inheritPrototype(subType, superType) {
    // 創建對象
    var prototype = Object(superType.prototype);
    // 增強對象
    prototype.constructor = subType;
    // 指定對象
    subType.prototype = prototype;
}

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
    alert(this.name);
};

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

    this.age = age;
}

inheritPrototype(SubType, SuperType);

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

 

參考文獻

[1]《JavaScript高級程序設計(第3版)》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章