公共类字段

公共字段是可写、可枚举和可配置的属性。因此,与它们的私有对应物不同,它们参与原型继承。

语法

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 形式,这在类字段标准化后导致了一些细微的错误。

示例

使用类字段

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

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 语言规范
# prod-FieldDefinition

浏览器兼容性

BCD 表格仅在启用 JavaScript 的浏览器中加载。

另请参阅