ECMAScript 2015 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。

定义类

类实际上是个“特殊的函数”,就像你能够定义的函数表达式函数声明一样,类语法有两个组成部分:类表达式类声明

类声明

定义一个类的一种方法是使用一个类声明。要声明一个类,你可以使用带有class关键字的类名(这里是“Rectangle”)。

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

提升

函数声明类声明之间的一个重要区别是函数声明会提升,类声明不会。你首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个ReferenceError

let p = new Rectangle(); 
// ReferenceError

class Rectangle {}

类表达式

一个类表达式是定义一个类的另一种方式。类表达式可以是被命名的或匿名的。赋予一个命名类表达式的名称是类的主体的本地名称。

/* 匿名类 */ 
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

/* 命名的类 */ 
let Rectangle = class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

注意: 类表达式也同样受到类声明中提到的提升问题的限制。

类体和方法定义

一个类的类体是一对花括号/大括号 {} 中的部分。这是你定义类成员的位置,如方法或构造函数。

严格模式

类声明和类表达式的主体都执行在严格模式下。比如,构造函数,静态方法,原型方法,getter和setter都在严格模式下执行。

构造函数

constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由class创建的对象。一个类只能拥有一个名为 “constructor”的特殊方法。如果类包含多个constructor的方法,则将抛出 一个SyntaxError

一个构造函数可以使用 super 关键字来调用一个父类的构造函数。

原型方法

参见方法定义

class Rectangle {
    // constructor
    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 Rectangle(10, 10);

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

静态方法

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.hypot(dx, dy);
    }
}

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

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

用原型和静态方法包装

当一个对象调用静态或原型方法时,如果该对象没有“this”值(或“this”作为布尔,字符串,数字,未定义或null) ,那么“this”值在被调用的函数内部将为 undefined。不会发生自动包装。即使我们以非严格模式编写代码,它的行为也是一样的,因为所有的函数、方法、构造函数、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

如果我们使用传统的基于函数的类来编写上述代码,那么基于调用该函数的“this”值将发生自动装箱。

function Animal() { }

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

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

let obj = new Animal();
let speak = obj.speak;
speak(); // global object

let eat = Animal.eat;
eat(); // global object

实例属性

实例的属性必须定义在类的方法里:

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

静态的或原型的数据属性必须定义在类定义的外面。

Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;

字段声明

公共和私有字段声明是JavaScript标准委员会TC39提出的实验性功能(第3阶段)。浏览器中的支持是有限的,但是可以通过Babel等系统构建后使用此功能。

公有字段声明

使用JavaScript字段声明语法,上面的示例可以写成:

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

通过预先声明字段,类定义变得更加自我记录,并且字段始终存在。

正如上面看到的,这个字段可以用也可以不用默认值来声明。

私有字段声明

使用私有字段,可以按以下方式细化定义。

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

从类外部引用私有字段是错误的。它们只能在类里面中读取或写入。通过定义在类外部不可见的内容,可以确保类的用户不会依赖于内部,因为内部可能在不同版本之间发生变化。

私有字段仅能在字段声明中预先定义。 

私有字段不能通过在之后赋值来创建它们,这种方式只适用普通属性。

更多信息,请看class fields.

使用 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.'

如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。

也可以继承传统的基于函数的“类”:

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

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

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

请注意,类不能继承常规(非可构造)对象。如果要继承常规对象,可以改用Object.setPrototypeOf()

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

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

Object.setPrototypeOf(Dog.prototype, Animal);// If you do not do this you will get a TypeError when you invoke speak

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

Species

你可能希望在派生数组类 MyArray 中返回 Array对象。这种 species 方式允许你覆盖默认的构造函数。

例如,当使用像Symbol.species 符号可以让你这样做:

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

Mix-ins

抽象子类或者 mix-ins 是类的模板。 一个 ECMAScript 类只能有一个单超类,所以想要从工具类来多重继承的行为是不可能的。子类继承的只能是父类提供的功能性。因此,例如,从工具类的多重继承是不可能的。该功能必须由超类提供。

一个以超类作为输入的函数和一个继承该超类的子类作为输出可以用于在ECMAScript中实现混合:

var calculatorMixin = Base => class extends Base {
  calc() { }
};

var randomizerMixin = Base => class extends Base {
  randomize() { }
};

使用 mix-ins 的类可以像下面这样写:

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }

规范

Specification Status Comment
ECMAScript 2015 (6th Edition, ECMA-262)
Class definitions
Standard Initial definition.
ECMAScript 2016 (ECMA-262)
Class definitions
Standard
ECMAScript 2017 (ECMA-262)
Class definitions
Standard
ECMAScript Latest Draft (ECMA-262)
Class definitions
Draft

浏览器兼容

Update compatibility data on GitHub
Desktop Mobile Server
Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet Node.js
classes Chrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13 Firefox Full support 45 IE No support No Opera Full support 36
Notes
Full support 36
Notes
Notes From Opera 29 to 35, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Safari Full support 9 WebView Android Full support 49
Notes
Full support 49
Notes
Notes From WebView 42 to 48, strict mode is required.
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45 Opera Android Full support 36
Notes
Full support 36
Notes
Notes From Opera 29 to 35, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Safari iOS Full support 9 Samsung Internet Android Full support 5.0
Notes
Full support 5.0
Notes
Notes In Samsung Internet 4.0, strict mode is required.
nodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
constructor Chrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13 Firefox Full support 45 IE No support No Opera Full support 36 Safari Full support 9 WebView Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45 Opera Android ? Safari iOS Full support 9 Samsung Internet Android Full support Yes nodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
extends Chrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13 Firefox Full support 45 IE No support No Opera Full support 36 Safari Full support 9 WebView Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45 Opera Android ? Safari iOS Full support 9 Samsung Internet Android Full support Yes nodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
Private class fields Chrome Full support 74 Edge No support No Firefox No support No IE No support No Opera Full support 62 Safari No support No WebView Android Full support 74 Chrome Android Full support 74 Firefox Android No support No Opera Android Full support 53 Safari iOS No support No Samsung Internet Android No support No nodejs Full support 12.0.0
Public class fields Chrome Full support 72 Edge No support No Firefox Full support 69 IE No support No Opera Full support 60 Safari No support No WebView Android Full support 72 Chrome Android Full support 72 Firefox Android No support No Opera Android Full support 51 Safari iOS No support No Samsung Internet Android No support No nodejs Full support 12.0.0
static Chrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13 Firefox Full support 45 IE No support No Opera Full support 36 Safari Full support 9 WebView Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45 Opera Android ? Safari iOS Full support 9 Samsung Internet Android Full support Yes nodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
Static class fields Chrome Full support 72 Edge No support No Firefox No support No
Notes
No support No
Notes
Notes Static fields aren't supported, see bug 1535804.
IE No support No Opera Full support 60 Safari No support No WebView Android Full support 72 Chrome Android Full support 72 Firefox Android No support No Opera Android Full support 51 Safari iOS No support No Samsung Internet Android No support No nodejs Full support 12.0.0

Legend

Full support  
Full support
No support  
No support
Compatibility unknown  
Compatibility unknown
See implementation notes.
See implementation notes.
User must explicitly enable this feature.
User must explicitly enable this feature.

在Scratchpad中运行


一个类不能被重新定义。如果你正在使用Scratchpad中的代码(Firefox菜单工具> Web Developer> Scratchpad),并且您运行了两次具有相同名称的类的定义,那么你将遇到一个令人困惑的SyntaxError:let <class name>。

要重新运行定义,请使用Scratchpad菜单 执行>重新加载并运行。
请为这个bug投票 #1428672

相关链接