类
描述
定义类
类实际上是“特殊的 函数”,就像你可以定义 函数表达式 和 函数声明 一样,类也可以通过两种方式定义:类表达式 或 类声明。
// 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
字段声明
使用类字段声明语法,constructor
示例可以写成
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 语言规范 # sec-class-definitions |
浏览器兼容性
BCD 表仅在启用 JavaScript 的浏览器中加载。