如何在 ES6 class 中创建 "public static field"?

How do I make a "public static field" in an ES6 class?

我正在制作一个 Java 脚本 class,我想要一个像 Java 中那样的 public 静态字段。这是相关代码:

export default class Agent {
    CIRCLE: 1,
    SQUARE: 2,
    ...

这是我得到的错误:

line 2, col 11, Class properties must be methods. Expected '(' but instead saw ':'.

ES6 模块似乎不允许这样做。有没有办法获得所需的行为,或者我必须写一个 getter?

在 ECMAScript 6 的当前草案中(截至 2015 年 2 月),所有 class 属性都必须是方法,而不是值(注意在 ECMAScript 中,"property" 在概念上类似于 OOP 字段,除了字段值必须是 Function 对象,而不是任何其他值,例如 NumberObject).

您仍然可以使用传统的 ECMAScript 构造函数指定这些 属性 说明符:

 class Agent {
 }
 Agent.CIRCLE = 1;
 Agent.SQUARE = 2;
 ...

您使用访问器和 "static" 关键字创建 "public static field":

class Agent {
    static get CIRCLE() {
      return 1;
    }
    static get SQUARE() {
      return 2;
    }
}

Agent.CIRCLE; // 1

查看规范,14.5 — Class 定义 — 您会看到一些可疑的相关内容 :)

ClassElement[Yield] :
  MethodDefinition[?Yield]
  static MethodDefinition[?Yield] ;

所以从那里你可以跟随 14.5.14 - 运行时语义:ClassDefinitionEvaluation - 仔细检查它是否真的像它看起来那样做。具体来说,第20步:

  1. For each ClassElement m in order from methods
    1. If IsStatic of m is false, then
      1. Let status be the result of performing PropertyDefinitionEvaluation for m with arguments proto and false.
    2. Else,
      1. Let status be the result of performing PropertyDefinitionEvaluation for m with arguments F and false.
    3. If status is an abrupt completion, then
      1. Set the running execution context’s LexicalEnvironment to lex.
      2. Return status.

IsStatic 在 14.5.9

中定义较早

ClassElement : static MethodDefinition
Return true.

所以 PropertyMethodDefinition 以 "F" (构造函数,函数对象)作为参数调用,而 creates an accessor method on that object.

这个 already works 至少在 IETP(技术预览)以及 6to5 和 Traceur 编译器中。

为了充分利用静态变量,我采用了这种方法。更具体地说,我们可以使用它来使用私有变量,或者只有 public getter,或者同时有 getter 或 setter。在最后一种情况下,它与上面发布的解决方案之一相同。

var Url = (() => {
    let _staticMember = [];
    return class {
        static getQueries(hash = document.location.hash) {
            return hash;
        }

        static get staticMember(){
            return _staticMember;
        }
    };
})();

Usages:
console.log(Url.staticMember); // [];
Url.staticMember.push('it works');
console.log(Url.staticMember); // ['it works'];

我可以创建另一个 class 扩展 Url 并且成功了。

我使用 babel 将我的 ES6 代码转换为 ES5

从 ECMAScript 2022 开始,您可以做这样的事情,类似于传统的面向 class 的语言,如 Java 和 C#:

class MyClass {
    static myStaticProp = 42;
    myProp = 42;
    myProp2 = this.myProp;
    myBoundFunc = () => { console.log(this.myProp); };

    constructor() {
        console.log(MyClass.myStaticProp); // Prints '42'
        console.log(this.myProp); // Prints '42'
        this.myBoundFunc(); // Prints '42'
    }
}

以上等同于:

class MyClass {
    constructor() {
        this.myProp = 42;
        this.myProp2 = this.myProp;
        this.myBoundFunc = () => { console.log(this.myProp); };

        console.log(MyClass.myStaticProp); // Prints '42'
        console.log(this.myProp); // Prints '42'
        this.myBoundFunc(); // Prints '42'
    }
}
MyClass.myStaticProp = 42;

这些功能已添加到 "Static Class Features" and "Class Fields" proposals by Daniel Ehrenberg et al. Google Chrome (and new Edge) started supporting both proposals in version 72, equivalent to Node.js 12+. Firefox supports public instance fields since version 69 and static instance fields since version 75. Safari supports both since version 14.1. See more info at caniuse.com

对于尚不支持这些功能的旧版浏览器,您可以使用 Babel to transpile class fields. This requires @babel/plugin-proposal-class-properties to be enabled (enabled by default in @babel/plugin-env starting from v7.14.0)。


与@kangax 声明 getter 的解决方案相比,此解决方案的性能也更高,因为这里直接访问 属性 而不是通过调用函数。


编辑:统一的 class 字段提案现在处于第 3 阶段。

编辑(2020 年 2 月):静态 class 功能已拆分为不同的提案。谢谢@GOTO0!

编辑(2021 年 3 月):除 Safari 外,2020 年 4 月之后发布的所有主要浏览器现在都支持此功能!

编辑(2021 年 6 月):两项提案均被 TC39、ECMAScript 语言委员会接受,Safari 在 14.1 版中提供了此功能!

@kangax 的回答并没有模仿传统 OOP 语言的整个静态行为,因为你不能像 const agent = new Agent; agent.CIRCLE; // Undefined

这样的实例访问静态 属性

如果您想像 OOP 一样访问静态 属性,这是我的解决方案:

class NewApp {
  get MULTIPLE_VERSIONS_SUPPORTED() {
    return this.constructor.MULTIPLE_VERSIONS_SUPPORTED; // Late binding for inheritance
  }
}

NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

测试代码如下

class NewApp {
  get MULTIPLE_VERSIONS_SUPPORTED() {
    console.log('this.constructor.name:', this.constructor.name); // late binding
    return this.constructor.MULTIPLE_VERSIONS_SUPPORTED;
  }
}

// Static property can be accessed by class
NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

const newApp = new NewApp;

// Static property can be accessed by it's instances
console.log('newApp.MULTIPLE_VERSIONS_SUPPORTED:', newApp.MULTIPLE_VERSIONS_SUPPORTED); // true

// Inheritance
class StandardApp extends NewApp {}

// Static property can be inherited
console.log('StandardApp.MULTIPLE_VERSIONS_SUPPORTED:', StandardApp.MULTIPLE_VERSIONS_SUPPORTED); // true

// Static property can be overwritten
StandardApp.MULTIPLE_VERSIONS_SUPPORTED = false;

const std = new StandardApp;

console.log('std.MULTIPLE_VERSIONS_SUPPORTED:', std.MULTIPLE_VERSIONS_SUPPORTED); // false