ES6 中Class建立對象與繼承實現

1 Class in ES6

ES6提出了類(Class)的概念,讓對象的原型的寫法更像面嚮對象語言寫法。 ES6中經過class定義對象,默認具備constructor方法和自定義方法,可是包含在class中的方法不可枚舉。app

class Point{
            constructor(){
            this.x=x;
            this.y=y;
            }
        toString(){
            return this.x+this.y;
            }
        }

注意:constructor方法對應ES5的構造函數;建立類的方法不須要使用function關鍵字,方法之間不須要逗號分隔。函數

ES5中定義對象時組合使用構造函數模式和原型模式,構造函數模式用於定義實例屬性,原型模式用於定義共享方法和共享屬性,節省內存,並且支持向構造函數傳遞參數。this

function Point (x,y){
            this.x=x;
        this.y=y;
        }
        Point.prototype.toString= function(){
            return this.x+this.y;
        }

2 ES5和ES6建立實例和對象對比

1)ES5中的Person構造函數和Person原型對象以及實例Person一、Person2的關係:構造函數的prototype屬性以及實例的__proto__屬性指向原型對象,原型對象的constructor屬性指向構造函數。
圖片描述spa

構造函數的原型(prototype)屬性在ES6中依舊存在,這一點和ES5同樣:類的方法都定義在類的原型(prototype)上,在類的實例上面調用方法,其實就是調用原型上的方法。prototype

//實例的constructor方法就是類的原型(prototype)的constructor方法
    class B{
      constructor(){}
    }
    let b = new B();
    console.log(b.constructor === B.prototype.constructor);//true

2)ES6的類能夠看作是構造函數的另外一種寫法(和ES5中構造函數等同的並非constructor方法):類是function類型,且類自己指向類的prototype對象的constructor屬性,這與ES5同樣。code

class Point{
          // ...
        }
        console.log(typeof Point) // "function"
        console.log(Point === Point.prototype.constructor) // true

3)使用類建立實例對象也是直接對類使用new命令,跟ES5中構造函數的用法一致。對象

4)類內部定義的方法都是不可枚舉的,這和ES5不一樣。
圖片描述圖片描述blog

5)constructor方法
一個類必須有constructor方法,若是沒有就默認添加constructor(){}。雖然類是函數,可是和ES5不一樣,不經過new而直接調用類會致使類型錯誤,也就是說這個函數僅當在和new一塊兒使用時纔有意義。
6)類的實例
與ES5同樣,實例的屬性除非顯式定義在其自己(即定義在this對象上),不然都是定義在原型上(即定義在class上)。繼承

class Point {
              constructor(x, y) {
                this.x = x;
                this.y = y;
              }
              toString() {
                return '(' + this.x + ', ' + this.y + ')';
              }
            }
            var point = new Point(2, 3);
            point.toString() // (2, 3)
            point.hasOwnProperty('x') // true
            point.hasOwnProperty('y') // true
            point.hasOwnProperty('toString') // false
            point.__proto__.hasOwnProperty('toString') // true

以上代碼中,x和y都是實例對象point自身的屬性(由於定義在this變量上),因此hasOwnProperty方法返回true,而toString是原型對象的屬性(由於定義在Point類上),因此hasOwnProperty方法返回false。這些都與ES5的行爲保持一致。圖片

與ES5同樣,類的全部實例共享一個原型對象。

var p1 = new Point(2,3);
        var p2 = new Point(3,2);
        p1.__proto__ === p2.__proto__
        //true

能夠經過實例的__proto__屬性爲Class原型添加方法,增長後全部的原型都具備這個方法,這和ES5同樣。

var p1 = new Point(2,3);
        var p2 = new Point(3,2);
        p1.__proto__.printName = function () { return 'Oops' };
        p1.printName() // "Oops"
        p2.printName() // "Oops"

7)Class不存在變量提高(hoist),這一點與ES5徹底不一樣。

new Foo(); // ReferenceError
        class Foo {}

上面代碼中,Foo類使用在前,定義在後,這樣會報錯,由於ES6不會把類的聲明提高到代碼頭部。這種規定的緣由與繼承有關,必須保證子類在父類以後定義。

8)嚴格模式
類和模塊的內部,默認就是嚴格模式,因此不須要使用use strict指定運行模式。只要你的代碼寫在類或模塊之中,就只有嚴格模式可用。考慮到將來全部的代碼,其實都是運行在模塊之中,因此ES6實際上把整個語言升級到了嚴格模式。

3 ES6 Class的繼承

Class經過extends關鍵字實現繼承,這和ES5經過修改原型鏈實現繼承不一樣(巧合的是,SASS也經過@extend實現樣式繼承):

class ColorPoint extends Point{}

子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工。若是不調用super方法,子類就得不到this對象。

class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 調用父類的constructor(x, y)
        this.color = color;
      }
      toString() {
        return this.color + ' ' + super.toString(); // super表明父類原型,調用父類的toString()
      }
    }

上面代碼中,constructor方法和toString方法之中,都出現了super關鍵字,它在這裏表示父類的構造函數,用來新建父類的this對象。
super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。第一種狀況,super做爲函數調用時,表明父類的構造函數,只能用在子類的構造函數中。ES6 要求,子類的構造函數必須執行一次super函數。第二種狀況,super做爲對象時,指代父類的原型對象。

ES5的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。(ES5經過原型模式的繼承:建立子類,而後將父類的實例賦值給子類原型,也就是重寫子類原型,代之以一個新類型的實例。)ES6的繼承機制徹底不一樣,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this。

若是子類沒有定義constructor方法,這個方法會被默認添加。在子類的構造函數中,只有調用super以後,纔可使用this關鍵字,不然會報錯。這是由於子類實例的構建,是基於對父類實例加工,只有super方法才能返回父類實例。

class Point {
          constructor(x, y) {
            this.x = x;
            this.y = y;
          }
        }
        
        class ColorPoint extends Point {
          constructor(x, y, color) {
            this.color = color; // ReferenceError
            super(x, y);
            this.color = color; // 正確
          }
        }

和ES5同樣,經過子類建立的實例是父類以及子類的實例:

let cp = new ColorPoint(25, 8, 'green');
        
        cp instanceof ColorPoint // true
        cp instanceof Point // true

4 補充

1)類的prototype屬性和__proto__屬性(這段還沒看明白,太繞了。)

ES5中每個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。ES6中沒有構造函數,Class做爲構造函數的語法糖,同時有prototype屬性和__proto__屬性,所以同時存在兩條繼承鏈。
(1)子類的__proto__屬性,表示構造函數的繼承,老是指向父類。
(2)子類prototype屬性的__proto__屬性,表示方法的繼承,老是指向父類的prototype屬性。
這樣的結果是由於,類的繼承是按照下面的模式實現的。

class A {
        }
        class B {
        }
        // B的實例繼承A的實例
        Object.setPrototypeOf(B.prototype, A.prototype);
        const b = new B();
        // B的實例繼承A的靜態屬性
        Object.setPrototypeOf(B, A);
        const b = new B();

這兩條繼承鏈,能夠這樣理解:做爲一個對象,子類(B)的原型(__proto__屬性)是父類(A);做爲一個構造函數,子類(B)的原型(prototype屬性)是父類的實例。

2)Object.getPrototypeOf

Object.getPrototypeOf方法能夠用來從子類上獲取父類。

Object.getPrototypeOf(ColorPoint) === Point
        // true

所以,可使用這個方法判斷,一個類是否繼承了另外一個類。

3)實例的__proto__屬性

子類實例的__proto__屬性的__proto__屬性,指向父類實例的__proto__屬性。也就是說,子類實例的原型的原型,是父類實例的原型。

var p1 = new Point(2, 3);
            var p2 = new ColorPoint(2, 3, 'red');
            p2.__proto__ === p1.__proto__ // false
            p2.__proto__.__proto__ === p1.__proto__ // true

總的來講,ES6是對ES5的形式上的改變,真實內容依舊不變,本質上依舊是經過原型鏈實現繼承。

相關文章
相關標籤/搜索