描述
定义类
类实际上是“特殊的函数”,就像你可以定义函数表达式和函数声明一样,类可以通过两种方式定义:类表达式或类声明。
// 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;
}
};
与函数表达式类似,类表达式可以是匿名的,或者拥有一个不同于其赋值变量的名称。然而,与函数声明不同,类声明与 let 或 const 具有相同的暂时死区限制,并且行为表现为不会被提升。
类体
类的身体是花括号 {} 中的部分。你可以在这里定义类成员,例如方法或构造函数。
即使没有 "use strict" 指令,类体也会在严格模式下执行。
类元素可以从三个方面进行描述:
- 种类:Getter、setter、方法或字段
- 位置:静态或实例
- 可见性:公共或私有
它们共同构成了 16 种可能的组合。为了更逻辑地划分参考资料并避免内容重叠,不同的元素在不同的页面中详细介绍。
- 方法定义
-
公共实例方法
- getter
-
公共实例 getter
- setter
-
公共实例 setter
- 公共类字段
-
公共实例字段
static-
公共静态方法、getter、setter 和字段
- 私有元素
-
所有私有元素
注意:私有元素有一个限制,即在同一个类中声明的所有私有名称都必须是唯一的。所有其他公共属性没有这个限制——你可以有多个同名的公共属性,最后一个会覆盖其他的。这与对象初始化器中的行为相同。
此外,还有两种特殊的类元素语法:constructor 和静态初始化块,它们有各自的引用。
构造函数
constructor 方法是用于创建和初始化类创建的对象的特殊方法。在一个类中只能有一个名为“constructor”的特殊方法——如果类中包含多个 constructor 方法,则会抛出 SyntaxError。
构造函数可以使用 super 关键字调用超类的构造函数。
你可以在构造函数中创建实例属性
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
或者,如果你的实例属性值不依赖于构造函数的参数,你可以将它们定义为类字段。
静态初始化块
静态初始化块允许对静态属性进行灵活初始化,包括在初始化期间评估语句,同时授予访问私有作用域的权限。
可以声明多个静态块,并且这些块可以与静态字段和方法的声明交错(所有静态项都按声明顺序进行评估)。
方法
方法在每个类实例的原型上定义,并由所有实例共享。方法可以是普通函数、异步函数、生成器函数或异步生成器函数。有关更多信息,请参阅方法定义。
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 关键字为类定义静态方法或字段。静态属性(字段和方法)定义在类本身上,而不是每个实例上。静态方法通常用于为应用程序创建实用函数,而静态字段则适用于缓存、固定配置或任何不需要在实例之间复制的数据。
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
字段声明
使用类字段声明语法,构造函数的示例可以写成:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
类字段类似于对象属性,而不是变量,因此我们不使用诸如 const 等关键字来声明它们。在 JavaScript 中,私有元素使用特殊的标识符语法,因此也不应使用 public 和 private 等修饰符关键字。
如上所示,字段可以带或不带默认值声明。没有默认值的字段默认为 undefined。通过提前声明字段,类定义变得更具自解释性,并且字段始终存在,这有助于优化。
有关更多信息,请参阅公共类字段。
私有元素
使用私有字段,定义可以如下完善。
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
从类外部引用私有字段是错误的;它们只能在类体内部读取或写入。通过定义类外部不可见的内容,你可以确保类的用户不会依赖于内部实现,这些内部实现可能会因版本而异。
私有字段只能在字段声明中提前声明。它们不能像普通属性那样通过赋值稍后创建。
私有方法和访问器也可以使用与其公共对应项相同的语法定义,但标识符以 # 开头。
有关更多信息,请参阅私有元素。
继承
extends 关键字用于类声明或类表达式,以创建作为另一个构造函数(类或函数)子类的类。
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(),然后才能使用 this。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.`);
}
}
const l = new Lion("Fuzzy");
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.
评估顺序
当class 声明或class 表达式被评估时,其各个组件按照以下顺序进行评估:
- 如果存在
extends子句,则首先进行评估。它必须评估为一个有效的构造函数或null,否则会抛出TypeError。 constructor方法被提取,如果constructor不存在,则用默认实现替换。然而,由于constructor定义只是一个方法定义,此步骤不可观察。- 类元素的属性键按声明顺序进行评估。如果属性键是计算得出的,则计算表达式将被评估,其
this值设置为围绕该类的this值(而不是类本身)。此时所有属性值都尚未被评估。 - 方法和访问器按声明顺序安装。实例方法和访问器安装在当前类的
prototype属性上,静态方法和访问器安装在类本身上。私有实例方法和访问器被保存以供稍后直接安装在实例上。此步骤不可观察。 - 类现在已使用
extends指定的原型和constructor指定的实现进行初始化。对于以上所有步骤,如果一个评估的表达式试图访问类的名称,则会抛出ReferenceError,因为类尚未初始化。 - 类元素的值按声明顺序进行评估
- 类现在已完全初始化,可以作为构造函数使用。
有关实例如何创建,请参阅constructor参考。
示例
使用实例和静态方法绑定 this
当静态方法或实例方法在没有 this 值的情况下被调用时,例如通过将方法赋值给一个变量然后调用它,方法内部的 this 值将是 undefined。即使没有 "use strict" 指令,这种行为也是相同的,因为 class 主体中的代码总是以严格模式执行。
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。
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® 2026 语言规范 # sec-class-definitions |
浏览器兼容性
加载中…