JavaScript ES6 class 關鍵字

在 ES6 中,引入了 Class (類別) 這個新的概念 (如果寫過 C++ 或 Java 等傳統語言應該非常熟悉),透過 class 這新的關鍵字,可以定義類別。

另外還引入了一些其他的新語法,來讓你更簡單直觀的用 JavaScript 寫 OOP (物件導向) 程式,但大多數語法只是語法糖 (syntactical sugar),並不是重新設計一套物件繼承模型 (object-oriented inheritance model),只是讓你更方便操作 JavaScript 既有的原型繼承模型 (prototype-based inheritance)

在 ES6 你可以用 class 語法定義一個類別:

class Animal { 
    constructor(name) {
        this.name = name;
    }
  
    speak() {
        console.log(this.name + ' makes a noise.');
    }
}

上面我們定義了一個 Animal 類別:

  • 其中 constructor 方法用來定義類別的建構子 (constructor)
  • 方法 (method) 的定義也有新語法,我們在 Animal 類別中定義了 speak() 方法,你可以看到新語法省略了 function 關鍵字和冒號 :

我們說過新語法只是語法糖,底層還是 prototype-based 的關係:

let a = new Animal('Elephant');

// true
a.constructor === Animal.prototype.constructor;

// true
a.speak === Animal.prototype.speak;

// true
Animal.prototype.constructor === Animal;

// "function"
typeof Animal;

extends

extends 關鍵字用作類別繼承:

class Animal { 
    constructor(name) {
        this.name = name;
    }
  
    speak() {
        console.log(this.name + ' makes a noise.');
    }
}

class Dog extends Animal {
    speak() {
        console.log(this.name + ' barks.');
    }
}

var d = new Dog('Mitzie');

// 顯示 Mitzie barks.
d.speak();

super

如果子類別 (sub-class) 有定義自己的 constructor,必須在 constructor 方法中顯示地調用 super(),來調用父類別的 constructor,否則會出現錯誤 - ReferenceError: this is not defined

而且在 sub-class constructor 中,必須先執行完 super() 後,才能引用 this 關鍵字,否則也會出現錯誤 - ReferenceError: this is not defined

這是因為在 ES6 中,是先建立父類別 (parent class) 的物件實例 this (所以必須先執行 super()),然後再用子類別的 constructor 修改 this。

class Car {
    constructor() {
        console.log('Creating a new car');
    }
}

class Porsche extends Car {
    constructor() {
        super();
        console.log('Creating Porsche');
    }
}

let c = new Porsche();

// 依序顯示
// Creating a new car
// Creating Porsche

super 關鍵字有兩種用法:

  1. 當作函數 super(),只能在子類別的 constructor 中使用,在其他地方用會報錯 - SyntaxError: 'super' keyword unexpected here
  2. 當作物件 super,在一般方法中使用,用來引用父類別的方法和屬性
class Cat { 
    constructor(name) {
        this.name = name;
    }
  
    speak() {
        console.log(this.name + ' makes a noise.');
    }
}

class Lion extends Cat {
    speak() {
        super.speak();
        console.log(this.name + ' roars.');
    }
}

let bigCat = new Lion('Hoo');

bigCat.speak();
// 依序顯示
// Hoo makes a noise.
// Hoo roars.

另外,透過 super 調用父類別的方法時,super 會綁定子類別的 this (而不是父類別的 this):

class A {
    constructor() {
        this.x = 1;
    }
    
    print() {
        console.log(this.x);
    }
}

class B extends A {
    constructor() {
        super();
        this.x = 2;
    }
  
    foo() {
        super.print();
    }
}

let b = new B();

// 顯示 2 而不是 1
b.foo();

static

static 關鍵字用來定義靜態方法 (static method)。

class StaticMethodCall {
    static staticMethod() {
        return 'Static method has been called';
    }
    
    static anotherStaticMethod() {
        // 你可以用 this 來調用其他的 static method
        return this.staticMethod() + ' from another static method';
    }
}

// 顯示 Static method has been called
StaticMethodCall.staticMethod();

// 顯示 Static method has been called from another static method
StaticMethodCall.anotherStaticMethod();

父類別上的靜態方法也可以透過 super 來調用:

class Triple {
    static triple(n) {
        if (n === undefined) {
            n = 1;
        }
        return n * 3;
  }
}

class BiggerTriple extends Triple {
    static triple(n) {
        return super.triple(n) * super.triple(n);
    }
}

// 3
console.log(Triple.triple());

// 18
console.log(Triple.triple(6));

var tp = new Triple();

// 81
console.log(BiggerTriple.triple(3));

// 報錯
// TypeError: tp.triple is not a function
console.log(tp.triple());