詳解es6 class語法糖中constructor方法和super的作用

  • 2019 年 10 月 3 日
  • 筆記

       大多數面向對象的編程語言都支持類和類繼承的特性,而JS卻不支持這些特性,只能通過其他方法定義並關聯多個相似的對象,這種狀態一直延續到了ES5。由於類似的庫層出不窮,最終還是在ECMAScript 6中引入了類的特性。本文將詳細介紹ES6中的類,ES6 的 class 屬於一種“語法糖”,所以只是寫法更加優雅,更加像面對對象的編程,其思想和 ES5 是一致的。

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

等同於

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

其中 constructor 方法是類的構造函數,是一個默認方法,通過 new 命令創建對象實例時,自動調用該方法。一個類必須有 constructor 方法,如果沒有顯式定義,一個默認的 consructor 方法會被默認添加。所以即使你沒有添加構造函數,也是會有一個默認的構造函數的。一般 constructor 方法返回實例對象 this ,但是也可以指定 constructor 方法返回一個全新的對象,讓返回的實例對象不是該類的實例。

下面好好分析一下 super 關鍵字的作用

super 這個關鍵字,既可以當做函數使用,也可以當做對象使用。這兩種情況下,它的用法完全不用。

1. 當做函數使用

class A {}  class B extends A {    constructor() {      super();  // ES6 要求,子類的構造函數必須執行一次 super 函數,否則會報錯。    }  }

註:在 constructor 中必須調用 super 方法,因為子類沒有自己的 this 對象,而是繼承父類的 this 對象,然後對其進行加工,而 super 就代表了父類的構造函數。super 雖然代表了父類 A 的構造函數,但是返回的是子類 B 的實例,即 super 內部的 this 指的是 B,因此 super() 在這裡相當於 “`A.prototype.constructor.call(this, props)“。

class A {    constructor() {      console.log(new.target.name); // new.target 指向當前正在執行的函數    }  }    class B extends A {    constructor() {      super();    }  }    new A(); // A  new B(); // B

可以看到,在 super() 執行時,它指向的是 子類 B 的構造函數,而不是父類 A 的構造函數。也就是說,super() 內部的 this 指向的是 B。

2. 當做對象使用

在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。

class A {    c() {      return 2;    }  }    class B extends A {    constructor() {      super();      console.log(super.c()); // 2    }  }    let b = new B();  

  上面代碼中,子類 B 當中的 super.c(),就是將 super 當作一個對象使用。這時,super 在普通方法之中,指向 A.prototype,所以 super.c() 就相當於 A.prototype.c()

 

通過 super 調用父類的方法時,super 會綁定子類的 this

class A {    constructor() {      this.x = 1;    }    s() {      console.log(this.x);    }  }    class B extends A {    constructor() {      super();      this.x = 2;    }    m() {      super.s();    }  }    let b = new B();  b.m(); // 2

上面代碼中,super.s() 雖然調用的是 A.prototytpe.s(),但是 A.prototytpe.s()會綁定子類 B 的 this,導致輸出的是 2,而不是 1。也就是說,實際上執行的是 super.s.call(this)。

 

由於綁定子類的 this,所以如果通過 super 對某個屬性賦值,這時 super 就是 this,賦值的屬性會變成子類實例的屬性。

class A {    constructor() {      this.x = 1;    }  }    class B extends A {    constructor() {      super();      this.x = 2;      super.x = 3;      console.log(super.x); // undefined      console.log(this.x); // 3    }  }    let b = new B();

上面代碼中,super.x 賦值為 3,這時等同於對 this.x 賦值為 3。而當讀取 super.x 的時候,調用的是 A.prototype.x,但並沒有 x 方法,所以返回 undefined。

 

注意,使用 super 的時候,必須顯式指定是作為函數,還是作為對象使用,否則會報錯。

class A {}  class B extends A {    constructor() {      super();      console.log(super); // 報錯    }  }

上面代碼中,console.log(super); 的當中的 super,無法看出是作為函數使用,還是作為對象使用,所以 JavaScript 引擎解析代碼的時候就會報錯。這是,如果能清晰的表明 super 的數據類型,就不會報錯。

最後,由於對象總是繼承其他對象的,所以可以在任意一個對象中,使用 super 關鍵字。

結語:
ES6 的 class 畢竟是一個“語法糖”,所以只要理解了 JavaScript 中對象的概念和面向對象的思想,class 就不難理解啦。