公共类字段

Baseline 已广泛支持

此功能已成熟,并可在多种设备和浏览器版本上运行。自 2022 年 9 月起,所有浏览器都已支持此功能。

公共字段是定义在每个类实例或类构造函数上的可写、可枚举、可配置的属性。

语法

js
class ClassWithField {
  instanceField;
  instanceFieldWithInitializer = "instance field";
  static staticField;
  static staticFieldWithInitializer = "static field";
}

还有一些额外的语法限制

  • 静态属性(字段或方法)的名称不能是 prototype
  • 类字段(静态或实例)的名称不能是 constructor

描述

本页面详细介绍了公共实例字段。

公共实例字段存在于类的每个已创建实例上。通过声明公共字段,可以确保该字段始终存在,并且类定义更具自文档性。

公共实例字段在基类构造时(在构造函数体运行之前)添加到实例中,或在子类中 super() 返回之后立即添加。没有初始化的字段被初始化为 undefined。像属性一样,字段名可以是计算得出的。

js
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,因为该类尚未初始化。awaityield 在此表达式中按预期工作。

js
class C {
  [Math.random()] = 1;
}

console.log(new C());
console.log(new C());
// Both instances have the same field name

在字段初始化器中,this 指的是正在构建的类实例,而 super 指的是基类的 prototype 属性,其中包含基类的实例方法,但不包含其实例字段。

js
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 值都不同,所以初始化器表达式可以访问实例特定的属性。)

js
class C {
  obj = {};
}

const instance1 = new C();
const instance2 = new C();
console.log(instance1.obj === instance2.obj); // false

表达式是同步评估的。您不能在初始化器表达式中使用 awaityield。(可以将初始化器表达式看作被隐式包装在一个函数中。)

由于类的实例字段在各自的构造函数运行之前添加,因此您可以在构造函数中访问字段的值。但是,由于派生类的实例字段在 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);
    this.field = 2;
  }
}

const instance = new Derived();
// Base constructor: undefined
// Derived constructor: 1
console.log(instance.field); // 2

字段逐个添加。字段初始化器可以引用其上方的字段值,但不能引用其下方的字段值。所有实例和静态方法都预先添加,并且可以访问,尽管如果它们引用下方正在初始化的字段,则调用它们可能不会按预期运行。

js
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 = … 不同。

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

注意: 在类字段规范使用 [[DefineOwnProperty]] 语义最终确定之前,大多数转译器,包括 Babeltsc,将类字段转换为 DerivedWithConstructor 形式,这在类字段标准化后导致了细微的错误。

示例

使用类字段

类字段不能依赖于构造函数的参数,因此字段初始化器通常为每个实例评估为相同的值(除非相同的表达式每次都可以评估为不同的值,例如 Math.random() 或对象初始化器)。

js
class Person {
  name = nameArg; // nameArg is out of scope of the constructor
  constructor(nameArg) {}
}
js
class Person {
  // All instances of Person will have the same name
  name = "Dragomir";
}

然而,即使声明一个空的类字段也是有益的,因为它表明了该字段的存在,这允许类型检查器和人工读者静态分析类的形状。

js
class Person {
  name;
  age;
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

上面的代码看起来是重复的,但考虑 this 动态变异的情况:显式字段声明清楚地表明了哪些字段肯定会出现在实例上。

js
class Person {
  name;
  age;
  constructor(properties) {
    Object.assign(this, properties);
  }
}

由于初始化器在基类执行后才评估,您可以访问基类构造函数创建的属性。

js
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

浏览器兼容性

另见