公有类字段
公有静态字段和公有实例字段都是可编辑、可枚举和可配置的属性。因此,不同于私有对应值(private counterpart)的是,它们参与原型的继承。
语法
js
class ClassWithInstanceField {
instanceField = "instance field";
}
class ClassWithStaticField {
static staticField = "static field";
}
class ClassWithPublicInstanceMethod {
publicMethod() {
return "hello world";
}
}
示例
公有静态字段
公有静态字段在你想要创建一个只在每个类里面只存在一份,而不会存在于你创建的每个类的实例中的属性时可以用到。你可以用它存放缓存数据、固定结构数据或者其他你不想在所有实例都复制一份的数据。
公有静态字段是使用关键字 static
声明的。我们在声明一个类的时候,使用 Object.defineProperty()
方法将公有静态字段添加到类的构造函数中。在类被声明之后,可以从类的构造函数访问公有静态字段。
js
class ClassWithStaticField {
static staticField = "static field";
}
console.log(ClassWithStaticField.staticField);
// 预期输出值:"static field"
没有设定初始化的字段将默认被初始化为 undefined
。
js
class ClassWithStaticField {
static staticField;
}
console.assert(Object.hasOwn(ClassWithStaticField, "staticField"));
console.log(ClassWithStaticField.staticField);
// 预期输出值:"undefined"
公有静态字段不会在子类中重复初始化,但我们可以通过原型链访问它们。
js
class ClassWithStaticField {
static baseStaticField = "base field";
}
class SubClassWithStaticField extends ClassWithStaticField {
static subStaticField = "sub class field";
}
console.log(SubClassWithStaticField.subStaticField);
// 预期输出值:"sub class field"
console.log(SubClassWithStaticField.baseStaticField);
// 预期输出值:"base field"
在初始化字段时,this
指向的是类的构造函数。你也可以通过名字引用构造函数,并使用 super
获取到存在的父类的构造函数。
js
class ClassWithStaticField {
static baseStaticField = "base static field";
static anotherBaseStaticField = this.baseStaticField;
static baseStaticMethod() {
return "base static method output";
}
}
class SubClassWithStaticField extends ClassWithStaticField {
static subStaticField = super.baseStaticMethod();
}
console.log(ClassWithStaticField.anotherBaseStaticField);
// 预期输出值:"base static field"
console.log(SubClassWithStaticField.subStaticField);
// 预期输出值:"base static method output"
公有实例字段
公有实例字段存在于类的每一个实例中。通过声明一个公有字段,我们可以确保该字段一直存在,而类的定义则会更加像是自我描述。
公有实例字段可以在基类的构造过程中(构造函数主体运行前)使用 Object.defineProperty()
添加,也可以在子类构造函数中的 super()
函数结束后添加。
js
class ClassWithInstanceField {
instanceField = "instance field";
}
const instance = new ClassWithInstanceField();
console.log(instance.instanceField);
// 预期输出值:"instance field"
没有设定初始化的字段将默认被初始化为 undefined
。
js
class ClassWithInstanceField {
instanceField;
}
const instance = new ClassWithInstanceField();
console.assert(Object.hasOwn(instance, "instanceField"));
console.log(instance.instanceField);
// 预期输出值:"undefined"
和属性(properties)一样,字段名可以由计算得出。
js
const PREFIX = "prefix";
class ClassWithComputedFieldName {
[`${PREFIX}Field`] = "prefixed field";
}
const instance = new ClassWithComputedFieldName();
console.log(instance.prefixField);
// 预期输出值:"prefixed field"
在初始化字段时,this
指向的是类正在构造中的实例。和公有实例方法相同的是:你可以在子类中使用 super
来访问父类的原型。
js
class ClassWithInstanceField {
baseInstanceField = "base field";
anotherBaseInstanceField = this.baseInstanceField;
baseInstanceMethod() {
return "base method output";
}
}
class SubClassWithInstanceField extends ClassWithInstanceField {
subInstanceField = super.baseInstanceMethod();
}
const base = new ClassWithInstanceField();
const sub = new SubClassWithInstanceField();
console.log(base.anotherBaseInstanceField);
// 预期输出值:"base field"
console.log(sub.subInstanceField);
// 预期输出值:"base method output"
因为类的实例字段是在对应的构造函数运行之前添加的,所以你可以在构造函数中访问字段的值。
js
class ClassWithInstanceField {
instanceField = "instance field";
constructor() {
console.log(this.instanceField);
this.instanceField = "new value";
}
}
const instance = new ClassWithInstanceField(); // 输出 "instance field"
console.log(instance.instanceField); // "new value"
但是,因为派生类的实例字段是在 super()
返回之后定义的,所以基类的构造函数无法访问派生类的字段。
js
class Base {
constructor() {
console.log("Base constructor:", this.field);
}
}
class Derived extends Base {
field = 1;
constructor() {
super();
console.log("Derived constructor:", this.field);
}
}
const instance = new Derived();
// Base constructor: undefined
// Derived constructor: 1
因为类字段是通过 [[Define]]
语义(本质上是 Object.defineProperty()
)添加的,所以派生类中的字段声明并不会调用基类中的 setter。此行为不同于在构造函数中使用 this.field = …
。
js
class Base {
set field(val) {
console.log(val);
}
}
class DerivedWithField extends Base {
field = 1;
}
const instance = new DerivedWithField(); // No log
class DerivedWithConstructor extends Base {
constructor() {
super();
this.field = 1;
}
}
const instance2 = new DerivedWithConstructor(); // Logs 1
公有方法
公有静态方法
关键字 static
将为一个类定义一个静态方法。静态方法不是在一个实例之上被调用,而是在类自身之上被调用。它们通常是工具函数,比如用来创建或者复制对象。
js
class ClassWithStaticMethod {
static staticMethod() {
return "static method has been called.";
}
}
console.log(ClassWithStaticMethod.staticMethod());
// 预期输出值:"static method has been called."
静态方法是在类的声明阶段用 Object.defineProperty()
方法添加到类的构造函数中的。静态方法是可编辑、不可枚举和可配置的。
公有实例方法
正如其名,公有实例方法是可以在类的实例中使用的。
js
class ClassWithPublicInstanceMethod {
publicMethod() {
return "hello world";
}
}
const instance = new ClassWithPublicInstanceMethod();
console.log(instance.publicMethod());
// 预期输出值:"hello world"
公有实例方法是在类的声明阶段用 Object.defineProperty()
方法添加到类中的。静态方法是可编辑、不可枚举和可配置的。
你可以使用生成器(generator)、异步和异步生成器方法。
js
class ClassWithFancyMethods {
*generatorMethod() {}
async asyncMethod() {}
async *asyncGeneratorMethod() {}
}
在实例的方法中,this
指向的是实例本身,你可以使用 super
访问到父类的原型,由此你可以调用父类的方法。
js
class BaseClass {
msg = "hello world";
basePublicMethod() {
return this.msg;
}
}
class SubClass extends BaseClass {
subPublicMethod() {
return super.basePublicMethod();
}
}
const instance = new SubClass();
console.log(instance.subPublicMethod());
// 预期输出值:"hello world"
getter
和 setter
是和类的属性绑定的特殊方法,分别会在其绑定的属性被取值、赋值时调用。使用 get 和 set 语法定义实例的公有 getter
和 setter
。
js
class ClassWithGetSet {
#msg = "hello world";
get msg() {
return this.#msg;
}
set msg(x) {
this.#msg = `hello ${x}`;
}
}
const instance = new ClassWithGetSet();
console.log(instance.msg);
// 预期输出值:"hello world"
instance.msg = "cake";
console.log(instance.msg);
// 预期输出值:"hello cake"
规范
Specification |
---|
ECMAScript Language Specification # prod-FieldDefinition |
浏览器兼容性
BCD tables only load in the browser
参见
- 所有 JS 类元素的语义
- v8.dev 站点中关于公有和私有类字段的文章