天天看點

javascript 的七種繼承方式(七)類的繼承

前言

前面我們已經介紹了javascript中6種繼承方式,這6中繼承方式都是基于es5的,那麼接下來我們要講的是es6中新增的一種繼承方式—— 類的繼承

在es6中新引進了類的概念,作為對象的模闆。類是對現實生活中一類具有相同特征的事物的抽象。相信學過後端語言的同學對類并不陌生,類的實質是一種引用資料類型,類似于byte,short,int,long,float,double等基本資料類型,但不同的是它是一種複雜的資料類型。因為它的本質是資料類型,而不是具體的資料是以不存在于記憶體中,不能直接被操作,隻有被執行個體化為對象時才變得可操作。

在JavaScript中通過 class 關鍵字來定義類,基本上es6中的類可以看做是一個文法糖,它的大部分功能,在ES5中也都可以做到。新的class 寫法隻是讓對象原型的寫法更加清晰,更像面向對象程式設計的文法而已。下面我們來看一段代碼:

class Point{
    constructor(x, y){
        this.x = x;
        this.y = y;
    }

    toString(){
        return `(${this.x},${this.y})`
    }
}
           

上面的代碼中就定義了一個類,類中有一個constructor方法,這就是構造方法,而this關鍵字則代表類的執行個體對象,也就是說ES5中的構造函數對應着ES6中的構造方法。

該類中除了構造方法還定義了一個toString方法。這裡需要注意的是,在定義類的方法的時候,前面不需要用function關鍵字修飾,直接把函數定義放進去就可以了。另外方法之間不需要逗号分隔,否則會報錯。

在使用的時候也是直接使用new關鍵字進行執行個體化,跟ES5中構造函數的用法完全一緻。

constructor方法

constructor方法是類的預設方法,通過new關鍵字來建立對象的執行個體時,會自動調用此方法,一個類必須要有constructor方法,如果沒有顯示定義則會自動添加一個預設的空的constructor方法。

與es5一樣,執行個體屬性除非顯示定義在其本身(即定義在this對象上),否則都是定義在原型上(即定義在class上),看下面代碼:

class Point{
    constructor(x, y){
        this.x = x;
        this.y = y;
    }

    toString(){
        return `(${this.x},${this.y})`
    }
}


var p = new Point(2,3);

p.toString();//(2,3)

p.hasOwnProperty('x'); //true
p.hasOwnProperty('y'); //true
p.hasOwnProperty('toString'); //false

p.__proto__.hasOwnProperty('toString') //true
           

上面代碼中,x和y都是執行個體對象p的自身屬性(因為定義在this變量上),是以hasOwnProperty方法傳回的是true,而toString是原型對象的屬性(因為定義在類Point上),是以hasOwnProperty傳回false。這些與es5的行為保持一緻。

類的所有執行個體共享一個原型對象

var p1 = Point(1,2);
var p2 = Point(2,3);

p1.__proto__ === p2.__proto__; ///true
           

類的繼承

class的繼承是通過extends關鍵字實作,然後通過supper方法來調用父類的構造方法,這比es5的通過修改原型鍊實作繼承,要清晰友善的多。

class ColorPoint extends Point{}
           

上面的代碼簡單的實作了類的繼承,定義了一個ColorPointer類通過關鍵字extends實作對類Point的繼承,這樣ColorPoint就繼承了Point的所有的屬性和方法,但是由于ColorPoint内部沒有任何代碼,是以現在這兩個類完全一樣,相當于複制了一個Point類,下面我們來補充一些代碼進來:

class ColorPoint extends Point{
    constructor(x,y,color){
        supper(x,y)//調用父類的constructor方法并傳入x和y
        this.color = color; //ColorPoint新增屬性color
    }

    toString(){
        return this.color + ' '+ super.toString();//調用父類toString()
    }
}
           

上面代碼中,constructor方法和toString方法中都出現了super關鍵字,它在這裡表示父類的構造函數,用來建立父類的this對象。

  1. 子類必須在constructor方法中調用super方法,否則建立執行個體時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然後對其進行加工,如果不調用super方法,子類就得不到this對象。
  2. 如果子類沒有顯示添加constructor方法,則這個方法會被預設添加。也就是說不管有沒有顯示定義,任何一個子類都有constructor方法
  3. 在子類的構造函數中,隻有調用super之後,才可以使用this關鍵字,否則會報錯,這是因為子類執行個體的建構,是基于對父類執行個體的加工,隻有super方法才能傳回父類執行個體。

類的proshtotype屬性和__proto__屬性

大多數浏覽器的es5實作中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。class作為構造函數的文法糖,同時有prototype屬性和__proto__屬性,是以同時存在兩條繼承鍊

  1. 子類的__proto__屬性,表示構造函數的繼承,總是指向父類
  2. 子類的prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性
class A{}

class B extends A{}

B.__proto__ === A //true
B.prototype.__proto__ === A.prototype //true
           

上面的代碼中,子類B的__proto__屬性執行父類A,子類B的prototype屬性的__proto__屬性指向父類A的prototype屬性。這樣的結果是因為,類的繼承是按照下面的模式實作的:

class A{}

class B{}

//B的執行個體繼承A的執行個體
Object.setPrototypeOf(B.prototype, A.prototype);

// B繼承A的靜态屬性
Object.setPrototypeOf(B, A);
           

其中Object.setPrototypeOf方法是按如下模式實作的:

Object.setPrototypeOf = function(obj, proto){
    obj.__proto__ = proto;
    return obj;
}
           

是以就得到上面代碼中的結果:

Object.setPrototypeOf(B.prototype, A.prototype)
//等同于
B.prototype.__proto__ = A.Prototype

Object.setPrototypeOf(B, A)
//等同于
B.__proto__ = A;
           

這兩天繼承鍊可以這樣了解:作為一個對象,子類B的原型__proto__屬性是父類A;作為一個構造函數,子類B的原型prototype屬性是父類的執行個體。

以上就是關于ES6中類的繼承。到此JavaScript中的七種繼承方式就介紹完了。