公共类字段
公共字段是定义在每个类实例或类构造函数上的可写、可枚举、可配置的属性。
语法
class ClassWithField {
instanceField;
instanceFieldWithInitializer = "instance field";
static staticField;
static staticFieldWithInitializer = "static field";
}
还有一些额外的语法限制
- 静态属性(字段或方法)的名称不能是
prototype。 - 类字段(静态或实例)的名称不能是
constructor。
描述
本页面详细介绍了公共实例字段。
公共实例字段存在于类的每个已创建实例上。通过声明公共字段,可以确保该字段始终存在,并且类定义更具自文档性。
公共实例字段在基类构造时(在构造函数体运行之前)添加到实例中,或在子类中 super() 返回之后立即添加。没有初始化的字段被初始化为 undefined。像属性一样,字段名可以是计算得出的。
const PREFIX = "prefix";
class ClassWithField {
field;
fieldWithInitializer = "instance field";
[`${PREFIX}Field`] = "prefixed field";
}
const instance = new ClassWithField();
console.log(Object.hasOwn(instance, "field")); // true
console.log(instance.field); // undefined
console.log(instance.fieldWithInitializer); // "instance field"
console.log(instance.prefixField); // "prefixed field"
计算得出的字段名只在类定义时评估一次。这意味着每个类总是有一组固定的字段名,两个实例不能通过计算得出的名称拥有不同的字段名。计算表达式中的 this 值是围绕类定义的 this,并且引用类的名称会引发 ReferenceError,因为该类尚未初始化。await 和 yield 在此表达式中按预期工作。
class C {
[Math.random()] = 1;
}
console.log(new C());
console.log(new C());
// Both instances have the same field name
在字段初始化器中,this 指的是正在构建的类实例,而 super 指的是基类的 prototype 属性,其中包含基类的实例方法,但不包含其实例字段。
class Base {
baseField = "base field";
anotherBaseField = this.baseField;
baseMethod() {
return "base method output";
}
}
class Derived extends Base {
subField = super.baseMethod();
}
const base = new Base();
const sub = new Derived();
console.log(base.anotherBaseField); // "base field"
console.log(sub.subField); // "base method output"
每次创建新实例时都会评估字段初始化器表达式。(因为每个实例的 this 值都不同,所以初始化器表达式可以访问实例特定的属性。)
class C {
obj = {};
}
const instance1 = new C();
const instance2 = new C();
console.log(instance1.obj === instance2.obj); // false
表达式是同步评估的。您不能在初始化器表达式中使用 await 或 yield。(可以将初始化器表达式看作被隐式包装在一个函数中。)
由于类的实例字段在各自的构造函数运行之前添加,因此您可以在构造函数中访问字段的值。但是,由于派生类的实例字段在 super() 返回之后定义,因此基类的构造函数无法访问派生类的字段。
class Base {
constructor() {
console.log("Base constructor:", this.field);
}
}
class Derived extends Base {
field = 1;
constructor() {
super();
console.log("Derived constructor:", this.field);
this.field = 2;
}
}
const instance = new Derived();
// Base constructor: undefined
// Derived constructor: 1
console.log(instance.field); // 2
字段逐个添加。字段初始化器可以引用其上方的字段值,但不能引用其下方的字段值。所有实例和静态方法都预先添加,并且可以访问,尽管如果它们引用下方正在初始化的字段,则调用它们可能不会按预期运行。
class C {
a = 1;
b = this.c;
c = this.a + 1;
d = this.c + 1;
}
const instance = new C();
console.log(instance.d); // 3
console.log(instance.b); // undefined
注意: 这对于私有字段更为重要,因为即使私有字段在下方声明,访问未初始化的私有字段也会抛出 TypeError。(如果未声明私有字段,则会引发早期 SyntaxError。)
由于类字段是使用 [[DefineOwnProperty]] 语义(本质上是 Object.defineProperty())添加的,因此派生类中的字段声明不会调用基类中的 setter。此行为与在构造函数中使用 this.field = … 不同。
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
示例
使用类字段
类字段不能依赖于构造函数的参数,因此字段初始化器通常为每个实例评估为相同的值(除非相同的表达式每次都可以评估为不同的值,例如 Math.random() 或对象初始化器)。
class Person {
name = nameArg; // nameArg is out of scope of the constructor
constructor(nameArg) {}
}
class Person {
// All instances of Person will have the same name
name = "Dragomir";
}
然而,即使声明一个空的类字段也是有益的,因为它表明了该字段的存在,这允许类型检查器和人工读者静态分析类的形状。
class Person {
name;
age;
constructor(name, age) {
this.name = name;
this.age = age;
}
}
上面的代码看起来是重复的,但考虑 this 动态变异的情况:显式字段声明清楚地表明了哪些字段肯定会出现在实例上。
class Person {
name;
age;
constructor(properties) {
Object.assign(this, properties);
}
}
由于初始化器在基类执行后才评估,您可以访问基类构造函数创建的属性。
class Person {
name;
age;
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Professor extends Person {
name = `Professor ${this.name}`;
}
console.log(new Professor("Radev", 54).name); // "Professor Radev"
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # prod-FieldDefinition |
浏览器兼容性
加载中…