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

Posted

技术标签:

【中文标题】如何在 ES6 类中创建“公共静态字段”?【英文标题】:How do I make a "public static field" in an ES6 class? 【发布时间】:2015-04-11 07:30:42 【问题描述】:

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

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 引擎实现? @Dai github.com/ModuleLoader/es6-module-loader 【参考方案1】:

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

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


Agent.CIRCLE; // 1

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

类元素[产量]: MethodDefinition[?Yield]静态 MethodDefinition[?Yield] ;

因此,您可以从那里关注14.5.14 — 运行时语义:ClassDefinitionEvaluation — 仔细检查它是否真的像看起来那样做。具体来说,第 20 步:

    对于每个 ClassElement m 按方法顺序排列
      如果 IsStatic of m 为假,则
        让 status 为使用参数 proto 和 false 对 m 执行 PropertyDefinitionEvaluation 的结果。
      否则,
        设 status 为对 m 执行 PropertyDefinitionEvaluation 的结果,参数为 F 和 false。
      如果状态是突然完成,则
        将正在运行的执行上下文的 LexicalEnvironment 设置为 lex。 返回状态。

IsStatic 在前面的 14.5.9 中定义

类元素:静态方法定义 返回真。

所以PropertyMethodDefinition 是用“F”(构造函数,函数对象)作为参数调用的,而它又是creates an accessor method on that object。

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

【讨论】:

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

从 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 等人的 "Static Class Features" 和 "Class Fields" 提案中添加的。 Google Chrome(和新 Edge)开始支持 version 72 中的这两个提案,相当于 Node.js 12+。 Firefox 从version 69 开始支持公共实例字段,从version 75 开始支持静态实例字段。自version 14.1 以来,Safari 都支持。在caniuse.com 上查看更多信息。

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


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


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

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

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

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

【讨论】:

我觉得相关提案其实是this one(静态类特性)。【参考方案3】:

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

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

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

【讨论】:

请注意,ES6 class 语法只是传统 JS 构造函数和原型的语法糖。 我认为您希望将这些属性放在原型上,而不是放在构造函数上,以便通过实例的属性引用可以看到它们。 @Pointy 我推断 OP 正在尝试存储常量以供参考(几乎就像 C#/.NET enum)。 @MattBrowne 是的,但要明确的是class 语法也有一些细微的差别。例如,用Class.prototype.method = function () ; 声明的方法是可枚举的(在for-in 循环中可见),而class 方法是不可枚举的。【参考方案4】:

为了充分利用静态变量,我采用了这种方法。更具体地说,我们可以使用它来使用私有变量或只有公共 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 = []; 不会简单得多吗? 那些=== 比较都产生false,顺便说一句 "Full Advantage" 的意思是,在上述方式中,您可以根据需要将 _staticMember 设为私有。【参考方案5】:

@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 实际上会发出警告/提示。 @Isac 是的,你是对的。不鼓励通过实例访问,我的回答也是如此。只是解决方案的另一个角度。 ?

以上是关于如何在 ES6 类中创建“公共静态字段”?的主要内容,如果未能解决你的问题,请参考以下文章

如何在javascript类中创建一个静态字段[重复]

如何在 Ruby 中创建私有类常量

如何设法在 C++ 中创建矢量类?

如何在 C# 中创建 COM 可见类?

Flutter:如何在我的 Style 类中创建构造函数? [复制]

如何在 C++ 中创建一个类的多个实例