ES6中的類繼承和ES5中的繼承模式詳解

餘大彬發表於2018-08-13

1、ES5中的繼承模式

我們先看ES5中的繼承。

既然要實現繼承,首先我們得要有一個父類。

Animal.prototype.eat = function(food) {
    console.log(this.name + '正在吃' + food);            
}
function Animal(name) {
    this.color = ['green','red','blue'];
    this.name = name || 'animal';
    this.sleep = function() {
        console.log(this.name + "正在睡覺")
    }
}

1.1、原型鏈繼承

原型鏈繼承核心: 將父類的例項作為子類的原型。

function Cat(name) {
    this.name = name
    this.color = ['green','red','blue'];//引用型別值,,所有例項會共享這個屬性。
}
Cat.prototype = new Animal();
var cat = new Cat('cat');
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat instanceof Animal);
console.log(cat.sleep());

原型鏈式繼承模式實現了子類對父類的原型的繼承。

但是,原型鏈式繼承並沒有實現程式碼的複用,一些共同的屬性:如name,在子類中還是得重新寫一遍(即同一套程式碼還是得重新寫)。

再者,cat繼承了Animal例項的所有屬性和方法,這些方法並不都是我們需要的,也就是過多的繼承了沒有用的屬性。且如果原型中包含引用型別值,那麼所有的例項會共享這個屬性。

1.2、建構函式模式

建構函式模式核心: 在子型別建構函式的內部呼叫超型別建構函式。

function Person(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}
function Student(name,age,sex){
    Person.call(this,name,age,sex);
    this.grade = grade;
}
let student = new Student;

優點:

  • 建構函式模式繼承實現了程式碼的複用

缺點:

  • 不能繼承借用的建構函式的原型,只能借用建構函式本身的屬性和方法
  • 每次建構函式都要多走一個函式

1.3、組合繼承

實現核心:組合繼承結合了上面兩種方式的繼承模式,即實現了程式碼複用,也實現呢了原型繼承。

function SuperType(name) {
    this.name = name;
    this.colors = ['red','blue','pink'];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}
function SubType(name,age) {
    //繼承屬性
    SuperType.call(this,name);//在建立例項時第二次呼叫SuperType
    this.age = age;
}

//繼承方法
SubType.prototype = new SuperType();//第一次呼叫SuperType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    console.log(this.age)
}

缺點:

  • 父類建構函式被呼叫2次,子類例項的屬性存在兩份,一份在原型上,一份在例項屬性上。造成記憶體的浪費。

1.4、寄生組合式繼承

寄生組合式繼承是對組合繼承的進一步優化。我們先看一下為什麼要寫這個語句。

SubType.prototype = new SuperType();

我們無非是想讓SubType繼承SuperType的原型。但是我們為什麼不直接寫成這樣呢?

SubType.prototype = SuperType.prototype

這樣寫確實可以實現子類物件對父類物件原型的繼承。但是這樣寫的話:所有繼承該父類的子類物件的原型都指向同一個了。也就是說SubType不能有自己的原型了。這顯然不是我們想要的。

既然不能直接繼承,那可不可以間接繼承SuperType.prototype呢。這就是最終的解決方案:寄生組合式繼承

我們讓一個函式去指向SuperType.prototype,然後讓SubType.prototype指向這個函式產生的物件不就可以了嘛。

function inherit(Target,Origin) {//實現寄生組合式繼承的核心函式
    function F() {};
    F.prototype = Origin.prototype; //F()的原型指向的是Origin
    Target.prototype = new F(); //Target的原型指向的是F()
    Target.prototype.constructor = Target; 
SubType.prototype.__proto__ == SuperType.prototype }
function SuperType(name) { this.name = name; this.colors = ['red','blue','pink']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name,age) { //繼承屬性 SuperType.call(this,name);//在建立例項時第二次呼叫SuperType this.age = age; } inherit(SubType,SuperType);//實現寄生組合式繼承

我們再來看一下實現寄生組合式繼承的核心函式。F函式其實是通用的,我們沒必要每次進入inherit函式時都宣告一遍。所以我們可以用閉包的形式來寫:

var inherit = (function () {
        var F = function () {};
        return function (Target , Origin) {
            F.prototype = Origin.prototype;//F()的原型指向的是Origin
            Target.prototype = new F();//Target的原型指向的是F()
            Target.prototype.constructor = Target;
            Target.prototype.uber = Origin.prototype;
SubType.prototype.__proto__ == SuperType.prototype } })()

2、ES6中的類繼承

我們先來看一下ES6裡面是如何定義一個類的。

class Parent {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    
    getName() {
        return this.name;
    }
}
typeof Parent; //function,類的資料型別就是函式,類本身就指向建構函式

上面程式碼定義了一個“類”,可以看到裡面有一個constructor方法,這就是構造方法,而this關鍵字則代表例項物件。也就是說,ES5的建構函式Parent,對應ES6的Parent類的構造方法。
Parent類除了構造方法,還定義了一個getName方法。

注意,定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函式定義放進去就可以了。另外,方法之間不需要逗號分隔,加了會報錯。類中定義的所有方法都是不可列舉的。

此外:類的所有方法都定義在類的prototype屬性上面。且class不存在變數提升,如果在class宣告之前呼叫,會報錯。

new Parent();
class Parent {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    
    getName() {
        return this.name;
    }
}
//Uncaught ReferenceError: Parent is not defined

2.1、類的constructor方法

constructor方法是類的預設方法,通過new命令生成物件例項時,自動呼叫該方法。一個類必須有constructor方法,

如果沒有顯式定義,一個空的constructor方法會被預設新增

2.2、類的繼承

class Child extends Parent {
    constructor(sex) {
        super();
        this.sex = sex;
        
    }
}
var child = new Child('xiaoyu',12,'man');

在子類的建構函式中,如果顯示宣告瞭constructor,則必須要顯示的呼叫super函式(這一點和Java有點不一樣)。

只有呼叫super之後,才可以使用this關鍵字,否則會報錯。

2.3、類的prototype屬性和__proto__屬性

大多數瀏覽器的ES5實現之中,每一個物件都有__proto__屬性,指向對應的建構函式的prototype屬性。

Class作為建構函式的語法糖,同時有 prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。

  • 子類的__proto__屬性,表示類的繼承,總是指向父類。
  • 子類prototype屬性,表示類的例項的繼承,類的例項的__proto__屬性總是指向類的prototype屬性。

這些特點和ES5的寄生組合式繼承完全一致,所以類的繼承可以看做是寄生組合式繼承的語法糖(簡單理解)。但實際上兩者實現的底層原理是完全不一樣的。因為阮一峰大大的書籍《ES6入門基礎》裡面認為ES6的繼承機制完全和ES5的繼承機制不同。阮一峰大大的書是這麼說的。

ES5的繼承,實質是先創造子類的例項物件this,然後再將父類的方法新增到this上面(Parent.apply(this))。ES6的繼承機制完全不同,實質是先創造父類的例項物件this(所以必須先呼叫super方法),然後再用子類的建構函式修改this。

所以對這方面有深入理解的小夥伴們可以說說自己的理解。共同進步。

先寫到這裡,後面如果有更多的理解再繼續新增。

相關文章