类元素

公有(public)和私有(private)字段声明是一个JavaScript标准委员会 TC39提议的 试验性功能 (第3阶段)。这项功能在浏览器中的支持还受限,但你可以通过 Babel等构建系统来使用它。参见下面的 兼容性信息

公有字段

静态公有字段和实例公有字段都是可编辑的,可遍历的,可配置的。它们本身不同于私有对应值(private counterparts)的是,它们参与原型的继承。

静态公有字段

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

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

class ClassWithStaticField {
  static staticField = 'static field';
}

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

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

class ClassWithStaticField {
  static staticField;
}

console.assert(ClassWithStaticField.hasOwnProperty('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(instance.hasOwnProperty('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"

公共方法

静态公共方法

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

静态方法是在类的赋值阶段用Object.defineProperty方法添加到类中的。静态方法是可编辑的、不可遍历的和可配置的。

公共实例方法

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

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

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

公共实例方法是在类的赋值阶段用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 worl​d"

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);
// expected output: "hello worl​d"

instance.msg = 'cake';
console.log(instance.msg);
// 预期输出值: "hello cake"

私有字段

静态私有字段

静态私有字段可以在类声明本身内部的构造函数上被访问到。

静态变量只能被静态方法访问的限制依然存在。

class ClassWithPrivateStaticField {
  static #PRIVATE_STATIC_FIELD;

  static publicStaticMethod() {
    ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD = 42;
    return ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD;
  }
}

assert(ClassWithPrivateStaticField.publicStaticMethod() === 42);

静态私有字段是在类赋值的时候被添加到类构造函数中的。

静态私有字段有一个来源限制。只有定义静态私有字段的类可以访问该字段。这在使用this时,可能会导致不符合预期的行为。

class BaseClassWithPrivateStaticField {
  static #PRIVATE_STATIC_FIELD;

  static basePublicStaticMethod() {
    this.#PRIVATE_STATIC_FIELD = 42;
    return this.#PRIVATE_STATIC_FIELD;
  }
}

class SubClass extends BaseClassWithPrivateStaticField { }

assertThrows(() => SubClass.basePublicStaticMethod(), TypeError);

私有实例字段

私有实例字段是通过# names句型(读作“哈希名称”)声明的,即为识别符加一个前缀“#”。“#”是名称的一部分,也用于访问和声明。

封装是语言强制实施的。引用不在作用域内的 # names 是语法错误。

class ClassWithPrivateField {
  #privateField;
  
  constructor() {
    this.#privateField = 42;
    this.#randomField = 666; # Syntax error
  }
}

const instance = new ClassWithPrivateField();
instance.#privateField === 42; // Syntax error

私有方法

静态私有方法

和静态公共方法一样,静态私有方法也是在类里面而非实例中调用的。和静态私有字段一样,它们也只能在类的声明中访问。

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

静态私有方法可以是生成器、异步或者异步生成器函数。

class ClassWithPrivateStaticMethod {
    static #privateStaticMethod() {
        return 42;
    }

    static publicStaticMethod() {
        return ClassWithPrivateStaticMethod.#privateStaticMethod();
    }
}

assert(ClassWithPrivateStaticField.publicStaticMethod() === 42);

私有实例方法

私有实例方法在类的实例中可用,它的访问方式的限制和私有实例字段相同。

class ClassWithPrivateMethod {
  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
      return #privateMethod();
  }
}

const instance = new ClassWithPrivateMethod();
console.log(instance.getPrivateMessage());
// 预期输出值: "hello worl​d"

私有实例方法可以是生成器、异步或者异步生成器函数。私有gettersetter也是可能的:

class ClassWithPrivateAccessor {
  #message;

  get #decoratedMessage() {
    return `✨${this.#message}✨`;
  }
  set #decoratedMessage(msg) {
    this.#message = msg;
  }

  constructor() {
    this.#decoratedMessage = 'hello world';
    console.log(this.#decoratedMessage);
  }
}

new ClassWithPrivateAccessor();
// 预期输出值: "✨hello worl​d✨"

浏览器兼容性

公共类字段

Update compatibility data on GitHub
Desktop Mobile Server
Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet Node.js
Public class fields Chrome Full support 72 Edge No support No Firefox Full support 69 IE No support No Opera Full support 60 Safari No support No WebView Android Full support 72 Chrome Android Full support 72 Firefox Android No support No Opera Android Full support 51 Safari iOS No support No Samsung Internet Android No support No nodejs Full support 12.0.0

Legend

Full support  
Full support
No support  
No support

私有类字段

Update compatibility data on GitHub
Desktop Mobile Server
Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet Node.js
Private class fields Chrome Full support 74 Edge No support No Firefox No support No IE No support No Opera Full support 62 Safari No support No WebView Android Full support 74 Chrome Android Full support 74 Firefox Android No support No Opera Android Full support 53 Safari iOS No support No Samsung Internet Android No support No nodejs Full support 12.0.0

Legend

Full support  
Full support
No support  
No support

另请参考: