基线 广泛可用

此功能已得到完善,可在许多设备和浏览器版本上使用。它从以下时间起在所有浏览器中可用: 2017 年 3 月.

类是创建对象的模板。它们将数据与处理这些数据的代码封装在一起。JS 中的类构建在 原型 之上,但也具有一些类独有的语法和语义。

有关更多示例和说明,请参阅 使用类 指南。

描述

定义类

类实际上是“特殊的 函数”,就像你可以定义 函数表达式函数声明 一样,类也可以通过两种方式定义:类表达式类声明

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

// Expression; the class is anonymous but assigned to a variable
const Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// Expression; the class has its own name
const Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

与函数表达式类似,类表达式可以是匿名的,也可以具有与分配给它的变量不同的名称。但是,与函数声明不同,类声明具有与 letconst 相同的 暂时性死区 限制,并且表现得好像它们 没有提升

类体

类的体是位于花括号 {} 中的部分。在这里,您定义类成员,例如方法或构造函数。

类的体在 严格模式 中执行,即使没有 "use strict" 指令。

类元素可以由三个方面来描述

  • 类型:getter、setter、方法或字段
  • 位置:静态或实例
  • 可见性:公共或私有

总共有 16 种可能的组合。为了更合理地划分参考内容并避免内容重叠,不同的元素将在不同的页面中详细介绍

方法定义

公共实例方法

getter

公共实例 getter

setter

公共实例 setter

公共类字段

公共实例字段

static

公共静态方法、getter、setter 和字段

私有属性

所有私有内容

注意:私有属性的限制是,在同一个类中声明的所有属性名必须是唯一的。所有其他公共属性都没有此限制 - 您可以拥有多个具有相同名称的公共属性,最后一个会覆盖其他属性。这与 对象初始化器 中的行为相同。

此外,还有两种特殊的类元素语法:constructor静态初始化块,它们有各自的参考内容。

构造函数

constructor 方法是用于创建和初始化使用类创建的对象的特殊方法。在一个类中只能有一个名为“constructor”的特殊方法 - 如果类包含多个 constructor 方法,则会抛出 SyntaxError

构造函数可以使用 super 关键字调用超类的构造函数。

您可以在构造函数中创建实例属性

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

或者,如果您的实例属性的值不依赖于构造函数的参数,您可以将它们定义为 类字段

静态初始化块

静态初始化块 允许灵活地初始化 静态属性,包括在初始化期间评估语句,同时授予对私有范围的访问权限。

可以声明多个静态块,这些块可以与静态字段和方法的声明交织在一起(所有静态项都按声明顺序进行评估)。

方法

方法定义在每个类实例的原型上,并由所有实例共享。方法可以是普通函数、异步函数、生成器函数或异步生成器函数。有关更多信息,请参阅 方法定义

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

const square = new Rectangle(10, 10);

console.log(square.area); // 100
console.log([...square.getSides()]); // [10, 10, 10, 10]

静态方法和字段

static 关键字定义类的静态方法或字段。静态属性(字段和方法)定义在类本身而不是每个实例上。静态方法通常用于为应用程序创建实用程序函数,而静态字段则用于缓存、固定配置或任何其他不需要在实例之间复制的数据。

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

  static displayName = "Point";
  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);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined

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

字段声明

使用类字段声明语法,constructor 示例可以写成

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

类字段类似于对象属性,而不是变量,因此我们不使用 const 等关键字来声明它们。在 JavaScript 中,私有属性 使用特殊的标识符语法,因此也不应使用 publicprivate 等修饰符关键字。

如上所示,字段可以带或不带默认值进行声明。没有默认值的字段默认值为 undefined。通过预先声明字段,类定义变得更加自记录,并且字段始终存在,这有助于优化。

有关更多信息,请参阅 公共类字段

私有属性

使用私有字段,定义可以改进如下。

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

从类外部引用私有字段是错误的;它们只能在类体内读取或写入。通过定义对类外部不可见的内容,您可以确保类的用户不依赖于内部内容,这些内容可能在版本之间发生变化。

私有字段只能在字段声明中预先声明。它们不能像普通属性那样通过分配来创建。

有关更多信息,请参阅 私有属性

继承

extends 关键字用于类声明类表达式中,以创建另一个构造函数(类或函数)的子类。

js
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // call the super class constructor and pass in the name parameter
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.

如果子类中存在构造函数,则需要先调用 super(),然后再使用 thissuper 关键字还可以用于调用超类相应的方法。

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

const l = new Lion("Fuzzy");
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.

评估顺序

当评估 class 声明class 表达式 时,其各个组件的评估顺序如下

  1. 如果存在,extends 子句首先进行评估。它必须评估为有效的构造函数或 null,否则会抛出 TypeError
  2. constructor 方法被提取出来,如果 constructor 不存在,则用默认实现替换。但是,由于 constructor 定义只是一个方法定义,因此此步骤不可见。
  3. 类元素的属性键按声明顺序进行评估。如果属性键是计算的,则会评估计算表达式,其中this值设置为包围类的this值(而不是类本身)。此时还没有评估任何属性值。
  4. 方法和访问器按声明顺序安装。实例方法和访问器安装在当前类的prototype属性上,而静态方法和访问器安装在类本身。私有实例方法和访问器被保存起来,以便稍后直接安装在实例上。此步骤不可观察。
  5. 现在,类使用由extends指定的原型和由constructor指定的实现进行初始化。对于上述所有步骤,如果评估的表达式尝试访问类的名称,则会抛出ReferenceError,因为类尚未初始化。
  6. 类元素的值按声明顺序进行评估。
    • 对于每个实例字段(公有或私有),都会保存其初始化表达式。初始化器在实例创建期间进行评估,在构造函数的开始(对于基类)或super()调用返回之前(对于派生类)立即进行评估。
    • 对于每个静态字段(公有或私有),其初始化器会使用设置为类本身的this进行评估,并在类上创建该属性。
    • 静态初始化块使用设置为类本身的this进行评估。
  7. 现在,类已完全初始化,可以作为构造函数使用。

有关如何创建实例的信息,请参见constructor 参考。

示例

使用实例和静态方法绑定 this

当静态或实例方法在没有this的值的情况下被调用时,例如,通过将方法分配给变量然后调用它,this值在方法内部将为undefined。即使没有使用"use strict" 指令,这种行为也是一样的,因为class主体内的代码始终在严格模式下执行。

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

const obj = new Animal();
obj.speak(); // the Animal object
const speak = obj.speak;
speak(); // undefined

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

如果我们在非严格模式下使用传统的基于函数的语法重写上述代码,则this方法调用将自动绑定到globalThis。在严格模式下,this的值保持为undefined

js
function Animal() {}

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

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

const obj = new Animal();
const speak = obj.speak;
speak(); // global object (in non–strict mode)

const eat = Animal.eat;
eat(); // global object (in non-strict mode)

规范

规范
ECMAScript 语言规范
# sec-class-definitions

浏览器兼容性

BCD 表仅在启用 JavaScript 的浏览器中加载。

另请参阅