公共类字段
公共字段是可写、可枚举和可配置的属性。因此,与它们的私有对应物不同,它们参与原型继承。
语法
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
示例
使用类字段
类字段不能依赖于构造函数的参数,因此字段初始化器通常对每个实例评估为相同的值(除非同一表达式每次都可以评估为不同的值,例如 Date.now()
或对象初始化器)。
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 语言规范 # prod-FieldDefinition |
浏览器兼容性
BCD 表格仅在启用 JavaScript 的浏览器中加载。