公有类字段

公有静态字段和公有实例字段都是可编辑、可枚举和可配置的属性。因此,不同于私有对应值(private counterpart)的是,它们参与原型的继承。

语法

class ClassWithInstanceField {
  instanceField = 'instance field'
}

class ClassWithStaticField {
  static staticField = 'static field'
}

class ClassWithPublicInstanceMethod {
  publicMethod() {
    return 'hello world'
  }
}

示例

公有静态字段

公有静态字段在你想要创建一个只在每个类里面只存在一份,而不会存在于你创建的每个类的实例中的属性时可以用到。你可以用它存放缓存数据、固定结构数据或者其他你不想在所有实例都复制一份的数据。

公有静态字段是使用关键字 static 声明的。我们在声明一个类的时候,使用 Object.defineProperty() 方法将公有静态字段添加到类的构造函数中。在类被声明之后,可以从类的构造函数访问公有静态字段。

class ClassWithStaticField {
  static staticField = 'static field'
}

console.log(ClassWithStaticField.staticField)
// 预期输出值:"static field" 

没有设定初始化的字段将默认被初始化为 undefined

class ClassWithStaticField {
  static staticField
}

console.assert(Object.hasOwn(ClassWithStaticField, 'staticField'))
console.log(ClassWithStaticField.staticField)
// 预期输出值:"undefined"

公有静态字段不会在子类中重复初始化,但我们可以通过原型链访问它们。

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 获取到存在的父类的构造函数。

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() 函数结束后添加。

class ClassWithInstanceField {
  instanceField = 'instance field'
}

const instance = new ClassWithInstanceField()
console.log(instance.instanceField)
// 预期输出值:"instance field"

没有设定初始化的字段将默认被初始化为 undefined

class ClassWithInstanceField {
  instanceField
}

const instance = new ClassWithInstanceField()
console.assert(Object.hasOwn(instance, 'instanceField'))
console.log(instance.instanceField)
// 预期输出值:"undefined"

和属性(properties)一样,字段名可以由计算得出。

const PREFIX = 'prefix';

class ClassWithComputedFieldName {
    [`${PREFIX}Field`] = 'prefixed field';
}

const instance = new ClassWithComputedFieldName();
console.log(instance.prefixField);
// 预期输出值:"prefixed field"

在初始化字段时,this 指向的是类正在构造中的实例。和公有实例方法相同的是:你可以在子类中使用 super 来访问父类的原型。

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"

因为类的实例字段是在对应的构造函数运行之前添加的,所以你可以在构造函数中访问字段的值。

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() 返回之后定义的,所以基类的构造函数无法访问派生类的字段。

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 = …

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

备注: 在类字段规范最终确定使用 [[Define]] 语义前,包括 Babeltsc 在内的大多数转译器都将类字段转换为 DerivedWithConstructor 的形式,这在类字段标准化后产生了一些错误。

公有方法

公有静态方法

关键字 static 将为一个类定义一个静态方法。静态方法不是在一个实例之上被调用,而是在类自身之上被调用。它们通常是工具函数,比如用来创建或者复制对象。

class ClassWithStaticMethod {
  static staticMethod() {
    return 'static method has been called.';
  }
}

console.log(ClassWithStaticMethod.staticMethod());
// 预期输出值:"static method has been called."

静态方法是在类的声明阶段用 Object.defineProperty() 方法添加到类的构造函数中的。静态方法是可编辑、不可枚举和可配置的。

公有实例方法

正如其名,公有实例方法是可以在类的实例中使用的。

class ClassWithPublicInstanceMethod {
  publicMethod() {
    return 'hello world'
  }
}

const instance = new ClassWithPublicInstanceMethod()
console.log(instance.publicMethod())
// 预期输出值:"hello world"

公有实例方法是在类的声明阶段用 Object.defineProperty() 方法添加到类中的。静态方法是可编辑、不可枚举和可配置的。

你可以使用生成器(generator)、异步和异步生成器方法。

class ClassWithFancyMethods {
  *generatorMethod() { }
  async asyncMethod() { }
  async *asyncGeneratorMethod() { }
}

在实例的方法中,this 指向的是实例本身,你可以使用 super 访问到父类的原型,由此你可以调用父类的方法。

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"

gettersetter 是和类的属性绑定的特殊方法,分别会在其绑定的属性被取值、赋值时调用。使用 getset 语法定义实例的公有 gettersetter

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

参见