Classes

ECMAScript 6 中引入了類別 (class) 作為 JavaScript 現有原型程式(prototype-based)繼承的語法糖。類別語法並不是要引入新的物件導向繼承模型到 JavaScript 中,而是提供一個更簡潔的語法來建立物件和處理繼承。

定義類別

類別實際上是一種特別的函數(functions),就跟你可以定義函數敘述和函數宣告一樣,類別的語法有兩個元件:類別敘述(class expressions)和類別宣告(class declarations)。

類別宣告

一個定義類別的方法是使用類別宣告(class declaration),要宣告一個類別,你要使用關鍵字 class 搭配類別名稱(此例為 "Polygon")。

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Hoisting

函數宣告類別宣告的一個重要差別在於函數宣告是 hoisted ,類別宣告則不是。 你需要先宣告你的類別,然後存取它,否則像是下面的程式碼就會丟出一個 ReferenceError:

var p = new Polygon(); // ReferenceError

class Polygon {}

類別敘述

類別敘述是定義類別的另一種方法。類別敘述可以有名稱或是無名稱。賦予一個有名稱類別敘述的名稱只在類別主體(class's body)中有作用。(但是類別敘述的名稱可以透過該類別(不是實例)的 .name 屬性存取。)

// unnamed
var Polygon = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// named
var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

注意:類別敘述跟上述提到的類別宣告一樣,都會受到hoisting的影響。

類別主體與方法定義

類別的主體指的是被大括號({})包含的部分,你可以在這裡面定義類別成員(members),例如方法(methods)或建構子(constructors)。

Strict mode

類別宣告類別敘述的主體都會以嚴格模式(strict mode)執行,也就是說,建構子、靜態方法和原型方法、getter及setter函數等都會以嚴格模式執行。

建構子

建構子(constructor)方法是一個特別的方法,用來建立和初始化一個類別的物件。一個類別只能有一個名為建構子(constructor)的特別方法。當類別中含有一個以上的建構子方法時,SyntaxError 將會被拋出。

一個建構子可以用關鍵字 super 來呼叫父類別的建構子。

原型方法

參見 method definitions

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  // Getter
  get area() {
    return this.calcArea();
  }
  // Method
  calcArea() {
    return this.height * this.width;
  }
}

const square = new Polygon(10, 10);

console.log(square.area); //100

靜態方法(Static methods)

關鍵字 static 定義了一個類別的靜態方法,靜態方法不需要實體化它所屬類別的實例就可以被呼叫,它也無法被已實體化的類別物件呼叫。靜態方法經常被用來建立給應用程式使用的工具函數。

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

    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;

        return Math.sqrt(dx*dx + dy*dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2)); // 7.0710678118654755

裝箱、原型方法及靜態方法

當一個靜態方法或原形方法被呼叫,但沒有一個物件的值與this綁定時,被呼叫的函數中this關鍵字會是undefined在此情況下,自動裝箱(autoboxing)不會發生。?即使我們在非嚴格模式中寫程式,此行為仍然會存在,這是因為所有的函式、定義方法、建構子、getters和setters都是在嚴格模式中執行。因此,若我們沒有定義this的值,this會是undefined

class Animal {
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined

Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined

若我們將上述程式用傳統的函式基礎類別(function based classes)表達,自動裝箱則會依據this在其被呼叫的函式中所綁定的值發生。

function Animal() { }

Animal.prototype.speak = function() {
  return this;
}

Animal.eat = function() {
  return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // 全域物件

let eat = Animal.eat;
eat(); // 全域物件

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');
d.speak(); // Mitzie barks.

若在子類別中有建構子(constructor),要使用this前則必須先呼叫super()函式。

你也可以擴充(extends)傳統的函式基礎"類別"。

function Animal (name) {
  this.name = name;
}

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

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

var d = new Dog('Mitzie');
d.speak(); // Mitzie barks.

注意類別並無法擴充一般(non-constructible不可建構的)物件。如果您想要繼承自一般的物件,可以使用Object.setPrototypeOf() (en-US)來達成。

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

class Dog {
  constructor(name) {
    this.name = name;
  }
}

// 如果你沒有用以下的方法,當你呼叫speak時會出現TypeError
Object.setPrototypeOf(Dog.prototype, Animal);

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

Species

你可能會希望在陣列的衍生類別 MyArray 中回傳陣列 (Array) ,Species 這個模式讓你能覆寫默認的建構子 (contructor)。

舉例來說,當你使用像 map() 這類會回傳默認建構子的方法時,你希望能回傳父物件 Array ,而不是 MyArray 物件。 Symbol.species (en-US) 符號讓你做到這件事:

class MyArray extends Array {
  // Overwrite species to the parent Array constructor
  static get [Symbol.species]() { return Array; }
}

var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true

super 呼叫父類別

關鍵字 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.');
  }
}

var l = new Lion('Fuzzy');
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.

ES5 繼承語法與 ES6 類別語法的比較

TBD

範例

TBD

規格

規格 狀態 評論
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Class definitions' in that specification.
Standard Initial definition.

瀏覽器相容性

We're converting our compatibility data into a machine-readable JSON format. This compatibility table still uses the old format, because we haven't yet converted the data it contains. Find out how you can help! (en-US)

Feature Chrome Firefox (Gecko) Edge Internet Explorer Opera Safari
Basic support 42.0[1]
49.0
45 (45) 13 No support No support 9.0
Feature Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile Chrome for Android
Basic support No support 45.0 (45) ? ? 9 42.0[1]
49.0

參見