JavaScript | 繼承

————————————————————————————————————————————————————————— 數組

繼承 - ECMAScript只支持實現繼承(依靠原型鏈),不支持接口繼承(函數沒有簽名)函數

原型鏈 this

  • 利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法,
  • 構造函數、原型、實例的關係:每一個構造函數都有一個原型對象,原型對象包含一個指向構造函數的指針。實例包含一個指向原型對象的內部指針,在建立實例以後即指向原型對象
  • 而當A原型對象的指針指向B個原型對象時(此時A原型對象與B實例同級),就造成了一條原型鏈。
  • 圖解:

    原型搜索機制:當讀取模式訪問一個實例屬性時,首先會在實例中搜索該屬性,若是沒有找到該屬性則沿着原型鏈向上查找 spa

    在例子<Demo-1>中,調用instance.getSuperValue(),先搜索實例instance,再搜索SubType.prototype,再搜索SuperType.protorype,最後一步才找到該方法。prototype

    默認的原型:全部的引用類型默認都繼承了Object,因此默認原型的指針都會指向Object.prototype,完整的原型鏈以下: 指針

    instance SubType.prototype SuperType.prototype Object.prototypecode

  • p.s.

    必須替換掉實例的原型後才能給實例添加方法 對象

    不能使用對象字面量建立原型方法,這樣作會重寫原型鏈,如<Demo-3>blog

  • 缺點:

    包含引用類型值(Function Object Array)的原型屬性會被全部實例共享,在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例,因此原先的實例屬性就變成了如今的原型屬性了。<Demo-4>繼承

    在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。

// "use strict";

// Demo - 1
// SuperType 擁有一個屬性和一個方法
// SubType 擁有一個屬性和一個方法,又從SuperType那裏繼承了一個屬性一個方法
function SuperType(){
    this.property = "111";
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.subproperty = "222";
}
// p.s.new操做以前,SubType.prototype指向的是function,不容許爲function()定義.getSubValue方法,因此要將添加方法放在修改原型指向以後
// 操做以後SubType.prototype指向SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){ // 必須在SubType替換原型以後才能定義
    return this.subproperty;
}
var instance = new SubType();
console.log(instance.property); // 111
console.log(instance.getSuperValue()); // 111
console.log(instance.subproperty); // 222
console.log(instance.getSubValue()); // 222
console.log(instance.constructor); // f SuperType(){} 本來SubType中的constructor屬性被重寫
// 重寫SuperType.getSuperValue()
// 若是要重寫這個方法,會屏蔽原來的方法
// 換句話說,當經過SubType的實例調用getSuperValue時調用的就是這個從新定義的方法,但經過SuperType的實例調用時還會繼續調用原來的方法
var beforeReWrite = new SuperType();
SuperType.prototype.getSuperValue = function(){
    console.log("rewrite");
}
console.log(instance.getSuperValue()); // rewrite,this.property = undefined
console.log(SuperType.prototype.getSuperValue()); // rewrite,this.property = undefined
console.log(beforeReWrite.getSuperValue());

// Demo - 2
// 確認原型和實例的關係
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
// 另外一種方法
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true

// Demo - 3
function SuperType2(){
    this.property = "1111";
}
SuperType2.prototype.getSuperValue = function(){
    return this.property;
}
function SubType2(){
    this.subproperty = "2222";
}
SubType2.prototype = new SuperType2();
SubType2.prototype = {
    getSubValue:function(){
        return this.subproperty;
    },
    someOtherMethod:function(){
        return false;
    }
}
var instance2 = new SubType2();
console.log(instance2 instanceof Object); // true
console.log(instance2 instanceof SuperType2); // false,原型鏈被切斷
console.log(instance2 instanceof SubType2); // true
// console.log(instance2.getSuperValue()); // error

// Demo - 4
function SuperType3(){
    this.colors = ["red","blue","green"];
}
function SubType3(){}
SubType3.prototype = new SuperType3();
var instance3 = new SubType3();
instance3.colors.push("black");
console.log(instance3.colors); // ["red", "blue", "green", "black"]
var instance4 = new SubType3();
console.log(instance4.colors); // ["red", "blue", "green", "black"]

 

借用構造函數(僞造對象 / 經典繼承)

  • 在子類型構造函數的內部調用超類型構造函數
  • 優勢:

    解決了單獨使用原型鏈共享引用類型值屬性的問題

    能夠在子類型構造函數中向超類型構造函數傳遞參數

  • 缺點:

    沒法避免構造函數模式存在的問題:方法都在構造函數中定義,沒法實現函數複用

// "use strict";

function SuperType(name) {
    this.name = name;
    this.colors = ["111", "222", "333"];
}

function SubType() {
    SuperType.call(this, "name1");
    this.age = 20;
}

var instance = new SubType();
instance.colors.push("444");
console.log(instance.colors); // ["111", "222", "333", "444"]
console.log(instance.name); // name1
console.log(instance.age); // 20
var instance2 = new SubType();
console.log(instance2.colors); // ["111", "222", "333"]

 

組合繼承(僞經典繼承)

  • 將原型鏈和借用構造函數組合,使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承
  • 對應建立對象 <組合使用構造函數模式和原型模式>
  • 優勢:最經常使用
  • 缺點:須要調用兩次超類型構造函數,一次在建立子函數原型時,另外一次在子函數構造函數內部。調用子類型構造函數時須要重寫屬性
// "use strict";
function SuperType(name) {
    this.name = name;
    this.colors = ["111", "222", "333"];
}
SuperType.prototype.sayName = function() {
    console.log(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() {
    console.log(this.age);
}
var instance1 = new SubType("hugh", 20);
instance1.colors.push("444");
console.log(instance1.colors); // ["111", "222", "333", "444"]
instance1.sayName(); // hugh
instance1.sayAge(); // 20
var instance2 = new SubType("dong", 21);
console.log(instance2.colors); // ["111", "222", "333"]
instance2.sayName(); // dong
instance2.sayAge(); // 21

 

原型式繼承

  • 對應建立對象 <動態原型模式>
  • 沒有使用嚴格意義上的構造函數,藉助已有的對象建立新對象
  • 優勢:

    在不想建立構造函數,只想讓一個對象與另外一個對象保持相似的狀況下,原型式繼承徹底能夠勝任

  • 缺點:

    包含引用類型值的屬性始終都會共享,就像原型模式同樣

// "use strict";
function object(o){
    function F(){} // 建立臨時性構造函數
    F.prototype = o; // 將傳入的對象做爲構造函數的原型
    return new F(); // 返回臨時類型的一個新實例
}

var person = {
    name:"hugh",
    friends:["111",'222','333']
};

var anotherPerson = object(person);
anotherPerson.name = "dong";
anotherPerson.friends.push("444");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "hehe";
yetAnotherPerson.friends.push("555");

console.log(person.friends); // ["111", "222", "333", "444", "555"]
console.log(person.name); // hugh
console.log(anotherPerson.friends); // ["111", "222", "333", "444", "555"]
console.log(anotherPerson.name); // dong
console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555"]
console.log(yetAnotherPerson.name); // hehe

// 使用Object.create()規範化原型式繼承
// 以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性
var otherPerson1 = Object.create(person);
otherPerson1.friends.push("666");
console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555", "666"]
var otherPerson2 = Object.create(person,{
    name:{
        value:"test"
    }
});
console.log(otherPerson2.name);

 

寄生式繼承

  • 對應建立對象 <寄生構造函數 / 工廠模式>
  • 建立一個僅用於封裝繼承過程的函數,在內部加強對象,最後返回對象
  • 示範集成模式時使用的object()函數不是必須的,任何可以返回新對象的函數都適用於此模式
  • 使用場景:在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式
  • 缺點:沒法作到函數複用,相似於構造函數模式
// "use strict";
function object(o) {
    function F() {} // 建立臨時性構造函數
    F.prototype = o; // 將傳入的對象做爲構造函數的原型
    return new F(); // 返回臨時類型的一個新實例
}

function createAnother(original) { // 接收的函數做爲新對象基礎的對象
    var clone = object(original);
    clone.sayHi = function() { // 添加新方法
        console.log('hi');
    };
    return clone;
}
var person = {
    name: "hugh",
    friends: ['111', '222', '333']
};
var person1 = createAnother(person);
person1.sayHi();
console.log(person1.name);
console.log(person1.friends);

 

寄生組合式繼承

  • 優勢:

    最理想的繼承範式

    解決組合繼承重寫屬性的問題,只調用了一次SuperType構造函數

    避免了在SubType.prototype上建立沒必要要的屬性

    原型鏈保持不變

    可以正常使用instanceofisPrototypeOf()

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

// 1.建立超類型原型的一個副本
// 2.爲建立的副本添加constructor屬性,彌補因重寫原型而失去的屬性
// 3.將新建立的對象(即副本)賦值給子類型的原型
function inheritProtoType(subType,superType){
    var prototype = object(superType.prototype); // 建立對象
    prototype.constructor = subType; // 加強對象
    subType.prototype = prototype; // 指定對象
}
function SuperType(name){
    this.name = name;
    this.colors= [1,2,3,4];
}
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);
}

相關文章
相關標籤/搜索