JavaScrip繼承方案


JavaScrip繼承方案

一、原型鏈繼承

166c2c0107fd80c7.png 構造函數、原型和實例之間的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個原型對象的指針。es6

原型鏈繼承核心: 將父類的實例做爲子類的原型。markdown

繼承的本質就是複製,即重寫原型對象,代之以一個新類型的實例,app

function Foo(name){
    this.color = ['red','blue','black']
    this.name = name
};
Foo.prototype.sayName = function () {
     return ('my name is '+this.name)
}
function Car(age){
    this.age = age
}
Car.prototype = new Foo('jack')

a = new Car(16)
b = new Car()
b.color.push('white') // 在Car的實例b的color屬性上添加了一個white
console.log(a.name,a.age)
console.log(a.color,a.sayName())
複製代碼

20210413164918.png 咱們在打印實例a的color屬性意外的發現多出來一個白色,這是由於在原型鏈的繼承中,引用類型會被全部的實例所共享,多個實例對引用類型的操做會被篡改。而不是單獨屬於本身的一份,dom

這也是原型鏈繼承存在的問題:函數

問題1:原型中包含的引用類型屬性將被全部實例共享;post

問題2:子類在實例化的時候不能給父類構造函數傳參;性能

爲此咱們想出來構造函數式繼承方法優化

二、借用構造函數繼承

基本思想:this

借用構造函數的基本思想就是利用call或者apply把父類中經過this指定的屬性和方法複製(借用)到子類建立的實例中。 由於this對象是在運行時基於函數的執行環境綁定的。也就是說,在全局中,this等於window,而當函數被做爲某個對象的方法調用時,this等於那個對象。 call、apply 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。spa

不知道this的指向的話能夠仔細看看這篇關於this的完整講解

This指向問題的超詳解

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.getColor = function () {
    return  this.colors
}
function SubType(){
    // 繼承了 SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green"
instance2.getColor() // 報錯  TypeError: instance2.getColor is not a function
複製代碼

當咱們想要使用父類原型上的方法時報錯,發現順着原型鏈上去查找,找不到getColor這個方法, 這是由於子類的實例不能繼承原型屬性/方法

核心:使用父類的構造函數來加強子類實例,等因而複製父類的實例屬性給子類(沒用到原型)

缺點: 方法都在構造函數中定義, 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法, 沒法實現函數複用,每一個子類都有父類實例函數的副本,影響性能

三、組合繼承

原理:使用原型鏈實現對原型屬性和方法的繼承,借用構造函數實現對實例屬性的繼承。 結合原型繼承和構造函數的繼承方法,對之進行優化,達到便可繼承原型的方法,引用類型也不會被全部實例所共享。

function Person  (name) {
             this.name = name;
             this.friends = ['小李','小紅'];
         };

         Person.prototype.getName = function () {
             return this.name;
         };

        function Parent (age) {
            Person.call(this,'老明');  //第二次調用構造函數
            this.age = age;
        };

        Parent.prototype = new Person('老明');  //這一步也很關鍵
        var result = new Parent(24);   // 第一次調用構造函數
            console.log(result.name);    //老明
        result.friends.push("小智");  //
        console.log(result.friends);  //['小李','小紅','小智']
        console.log(result.getName());  //老明
        console.log(result.age);    //24

        var result1 = new Parent(25);   //經過借用構造函數都有本身的屬性,經過原型享用公共的方法
        console.log(result1.name);  //老明
        console.log(result1.friends);  //['小李','小紅']
複製代碼

優勢:

結合原型鏈和借用構造函數繼承兩種方法,取長補短。實現了函數複用,又可以保證每一個子類不會共享父類的引用類型屬性。

缺點:

調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。

四. 寄生組合式繼承

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype,{}); // 建立對象,建立父類原型的一個副本
  prototype.constructor = subType;                    // 加強對象,彌補因重寫原型而失去的默認的constructor 屬性
  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);
}

var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]

複製代碼

這個例子的高效率體如今它只調用了一次SuperType 構造函數,而且所以避免了在SubType.prototype 上建立沒必要要的、多餘的屬性。於此同時,原型鏈還能保持不變; 所以,還可以正常使用instanceof 和isPrototypeOf() 這是最成熟的方法,也是如今庫實現的方法

5、class

在 es6 中,官方給出了 class 關鍵字來實現面向對象風格的寫法,但本質上是寄生組合式繼承的語法糖。

class Person {
    constructor(age) {
        this.age_ = age;
    }
    sayAge() {
        console.log(this.age_);
    }
    // 靜態方法
    static create() {
        // 使用隨機年齡建立並返回一個 Person 實例
        return new Person(Math.floor(Math.random()*100));
    }
}
// 繼承普通構造函數
class Doctor extends Person {}

const doctor = new Doctor(32);
doctor.sayAge(); // 32
複製代碼

總結

1.ES5的繼承實質上是先建立子類的實例對象,而後再將父類的方法添加到this上(Parent.apply(this)).

2.ES6的繼承機制徹底不一樣,實質上是先建立父類的實例對象this(因此必須先調用父類的super()方法),而後再用子類的構造函數修改this。

3.ES5的繼承時經過原型或構造函數機制來實現。

4.ES6經過class關鍵字定義類,裏面有構造方法,類之間經過extends關鍵字實現繼承。

juejin.cn/post/684490…

相關文章
相關標籤/搜索