Classes

Baseline Widely available *

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2016.

* Some parts of this feature may have varying levels of support.

類別 (class) 是在 ECMAScript 6 中引入,用來作為建立新物件的模板。它能將程式碼封裝起來便於處理。 類別基於原型(prototype),但在語法和定義上,與 ES5 的類類別(class-like)有所不同。

定義類別

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

類別宣告

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

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

Hoisting

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

js
var p = new Polygon(); // ReferenceError

class Polygon {}

類別敘述

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

js
// 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

js
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 定義了一個類別的靜態方法,靜態方法不需要實體化它所屬類別的實例就可以被呼叫,它也無法被已實體化的類別物件呼叫。靜態方法經常被用來建立給應用程式使用的工具函數。

js
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

Instance properties

Instance properties 必須在 class 內定義:

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

宣告欄位

宣告公共欄位

在 JavaScript 以宣告欄位改寫上面的程式,可以寫成如下:

js
class Rectangle {
  height = 0;
  width;
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

通過事前宣告欄位,類別定義變得更加 self-documenting ,並且欄位會一直存在。

另外,如上面例子所示,聲明字段可以不用設定默認值。

想知道更多資訊,可參閱 public class fields

宣告私有欄位

私有欄位的宣告,可以參考下方例子:

js
class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {
    this.#height = height;
    this.#width = width;
  }
}

從類別外部使用私有欄位會出現錯誤;私有欄位只能在類別內讀寫。

通過宣告這些在類別以外不可見的東西,你可以確保類別的使用者不會倚賴類別的內部結構,畢竟一個類別的內部結構可能會隨著版本的更新而有所變化。

備註: 私有欄位只能在建立類別時事先宣告。

私有欄位不像普通欄位一樣,可以在類別宣告完後才建立。

想知道更多資訊,可參閱 private class features

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

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

js
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 在其被呼叫的函式中所綁定的值發生。

js
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 是在類別宣告或是類別敘述中建立子類別的方法。

js
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)傳統的函式基礎"類別"。

js
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()來達成。

js
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 符號讓你做到這件事:

js
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 是用來提供一個類別呼叫其父類別的函數。

js
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

規格

Specification
ECMAScript Language Specification
# sec-class-definitions

瀏覽器相容性

Report problems with this compatibility data on GitHub
desktopmobileserver
Chrome
Edge
Firefox
Opera
Safari
Chrome Android
Firefox for Android
Opera Android
Safari on iOS
Samsung Internet
WebView Android
WebView on iOS
Deno
Node.js
classes
constructor
extends
Private class fields
Private class fields 'in'
Private class methods
Public class fields
static
Static class fields
Class static initialization blocks

Legend

Tip: you can click/tap on a cell for more information.

Full support
Full support
Partial support
Partial support
Has more compatibility info.

參見