如何在 ES6 类中创建“公共静态字段”?

IT技术 javascript ecmascript-6 class-fields
2021-03-09 07:29:15

我正在制作一个 Javascript 类,我想要一个像 Java 一样的公共静态字段。这是相关代码:

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

这是我得到的错误:

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

看起来 ES6 module不允许这样做。有没有办法获得所需的行为,还是我必须编写一个吸气剂?

5个回答

您使用访问器和“静态”关键字创建“公共静态字段”:

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

Agent.CIRCLE; // 1

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

ClassElement[Yield] :
  MethodDefinition[?Yield]
  静态MethodDefinition[?Yield] ;

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

  1. 对于每个 ClassElement m 按顺序从方法
    1. 如果m 的 IsStatic 为 false,则
      1. 让 status 是对带有参数 proto 和 false 的 m 执行 PropertyDefinitionEvaluation 的结果。
    2. 别的,
      1. 让 status 是对带有参数 F 和 false 的 m 执行 PropertyDefinitionEvaluation 的结果。
    3. 如果状态是突然完成,则
      1. 将运行执行上下文的 LexicalEnvironment 设置为 lex。
      2. 返回状态。

IsStatic在早期定义14.5.9

ClassElement : 静态 MethodDefinition
返回 true。

SoPropertyMethodDefinition以“F”(构造函数,函数对象)作为参数调用,它反过来在该对象上创建一个访问器方法

至少已经在 IETP(技术预览)以及 6to5 和 Traceur 编译器中起作用。

至少从 Node.js 6.x+ 开始,这是支持的。
2021-04-26 07:29:15
对于其他人来说,Node 尚不支持静态访问器属性。:-/ kangax.github.io/compat-table/es6 / ...
2021-05-01 07:29:15
Kangax,也许我错了,但我认为您是面料背后的开发人员,或者至少您非常精通面料。为了继续我的项目,我有两个悬而未决的问题需要解决。你能不能这么好心看看他们?
2021-05-04 07:29:15
这对我不起作用,我得到``` 未处理的拒绝类型错误:无法设置类集合的属性 dataHashKey { api_1 | 静态获取 dataHashKey() { api_1 | 返回“集合”;api_1 | }```
2021-05-09 07:29:15
请注意,如果您使用 flow,则必须unsafe.enable_getters_and_setters=true在 .flowconfig 下添加一行[options](这很烦人)。
2021-05-15 07:29:15

从 ECMAScript 2022 开始,你可以做这样的事情,类似于传统的面向类的语言,比如 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;

这些特征是由 Daniel Ehrenberg 等人“静态类特征”“类字段”提案中添加的。谷歌浏览器(和新的 Edge)在版本 72 中开始支持这两个提案,相当于Node.js 12+Firefox 自版本 69起支持公共实例字段,自版本 75起支持静态实例字段Safari 从14.1 版开始支持两者caniuse.com 上查看更多信息

对于尚不支持这些功能的旧浏览器,您可以使用Babel转译类字段。这需要启用@babel/plugin-proposal-class-properties(默认情况下在 @babel/plugin-env 从v7.14.0开始启用)。


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


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

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

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

编辑(2021 年 6 月):两个提案都被TC39接受,ECMAScript 语言委员会和 Safari 在 14.1 版中发布了此功能!

我认为相关的建议实际上是这个静态类功能)。
2021-05-04 07:29:15

在 ECMAScript 6 的当前草案中(截至 2015 年 2 月),所有类属性必须是方法,而不是值(注意在 ECMAScript 中,“属性”在概念上类似于 OOP 字段,但字段值必须是Function对象,而不是任何其他值,例如 aNumberObject)。

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

 class Agent {
 }
 Agent.CIRCLE = 1;
 Agent.SQUARE = 2;
 ...
@Pointy 我推断 OP 正在尝试存储常量以供参考(几乎就像 C#/.NET enum)。
2021-04-23 07:29:15
@MattBrowne 是的,但要清楚class语法也有某些细微差别。例如,用 with 声明的方法Class.prototype.method = function () {};可枚举的(在 for-in 循环中可见),而class方法是不可枚举的。
2021-04-24 07:29:15
请注意,class无论如何,ES6语法只是传统 JS 构造函数和原型的语法糖。
2021-05-03 07:29:15
我认为您希望将这些属性放在原型上,而不是放在构造函数上,以便它们可以通过实例的属性引用可见。
2021-05-10 07:29:15

为了充分利用静态变量,我遵循了这种方法。更具体地说,我们可以使用它来使用私有变量或只有公共 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'];

我可以创建另一个扩展 Url 的类并且它起作用了。

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

什么是“充分优势”?会不会class Url { static getQueries… }; Url.staticMember = [];简单得多?
2021-04-17 07:29:15
“完全优势”意味着,如果需要,您可以通过上述方式将 _staticMember 保持为私有。
2021-05-11 07:29:15
这些===比较都产生了false,顺便说一句
2021-05-13 07:29:15

@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

static通过一个实例访问一个字段是相当罕见的,你不是说吗?在某些语言(例如 Java)中,如果您执行类似操作,IDE 实际上会发出警告/提示。
2021-04-28 07:29:15
@Isac 是的,你是对的。不鼓励按实例访问,我的回答也是如此。只是解决方案的另一个视角。😀
2021-04-28 07:29:15