在ES6类中声明静态常量?

407

我希望在一个class中实现常量,因为这是将常量放置在代码中的合理位置。

到目前为止,我一直在使用静态方法来实现以下解决方案:

class MyClass {
    static constant1() { return 33; }
    static constant2() { return 2; }
    // ...
}

我知道可以在原型中进行更改,但是很多人建议不要这样做。

在ES6类中实现常量有更好的方法吗?


12
个人而言,我只使用大写的变量名,并告诉自己不要改动它们 ;) - twicejr
3
@twicejr 我认为这并不相同,因为静态变量可以在没有先实例化该类的对象的情况下被访问。 - Lucas
在这个重复的答案很好中。 - undefined
@Cadoiz 这不就是与下面的答案一样吗? - undefined
@twicejr所说的是,许多编程语言中的常量都是用大写字母表示的惯例。因此,在我的类中,我也使用了静态常量:static MY_CONSTANT = 'ABC'; - undefined
20个回答

492

这里有几件事情可以做:

模块 中导出一个const。根据你的使用情况,你可以只需:

export const constant1 = 33;

在必要的模块中导入它。或者,基于您的静态方法的想法,您可以声明一个static get访问器

const constant1 = 33,
      constant2 = 2;
class Example {

  static get constant1() {
    return constant1;
  }

  static get constant2() {
    return constant2;
  }
}

这样,您就不需要括号:

const one = Example.constant1;

Babel REPL示例

那么,像你说的一样,由于一个class只是一个函数的语法糖,因此您可以添加一个不可写的属性,如下所示:

class Example {
}
Object.defineProperty(Example, 'constant1', {
    value: 33,
    writable : false,
    enumerable : true,
    configurable : false
});
Example.constant1; // 33
Example.constant1 = 15; // TypeError

如果我们能够像这样做一些事情,那将会很好:

class Example {
    static const constant1 = 33;
}

但是不幸的是,这个类属性语法仅存在于ES7提案中,而且即使如此,它也不能允许在属性中添加const


有没有确认静态属性像这样的东西只计算一次,或者更安全的方法是使用IIFE并在IIFE中手动添加属性,以避免重复构造返回值。我担心如果getter的结果非常重,比如一个100000个条目的JSObject,那么可怜的getter每次调用时都必须构造它。通过performance.now/date diff很容易测试,但它可能会以不同的方式实现,肯定更容易将getter实现为文字评估而不是是否为常量的高级决策。 - Dmytro
6
尽管上述方法巧妙地向类添加了一个常量属性,但该常量的实际值位于类定义的“{}”之外,这实际上违反了封装的定义之一。我认为只需在类内部定义一个常量属性即可,此时不需要使用get方法。 - NoChance
1
@NoChance 说得好。那只是举例说明。如果需要,getter方法完全可以完全封装值。 - CodingIntrigue
1
期待使用ES7提案,因为它看起来更自然,并且等同于大多数面向对象语言。 - Sangimed
这是一个更好的答案。 - toddmo
显示剩余2条评论

81
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

对我来说似乎有效。


4
@PirateApp 是一个静态方法,可以在任何地方访问,甚至可以从类实例内部访问。但是,由于它是静态的,所以无法从 Whatever 实例内部使用 this.MyConst,您必须始终像这样编写:Whatever.MyConst - Chunky Chunk
1
静态getter可能是目前最干净的解决方案。 - qwr
2
我也做过这个,但它与“静态”的定义存在根本性冲突,因为返回值在实例之间不共享。虽然它很方便,但最终是一个糟糕的选择。 - Madbreaks
2
如果你希望它看起来和作为一个常量的话,你可能还想定义相应的 setter,例如 static set MyConst(value) { throw new Error('cannot redefine constant'); },以避免 @jave.web提到的静默错误。 - jbuhacoff
Object.defineProperty 让我实现了类似于 Java 常量的功能,非常不错。 - FtheBuilder
显示剩余3条评论

22

我正在使用 babel,并且以下语法适用于我:

class MyClass {
    static constant1 = 33;
    static constant2 = {
       case1: 1,
       case2: 2,
    };
    // ...
}

MyClass.constant1 === 33
MyClass.constant2.case1 === 1

请注意您需要预设值"stage-0"
安装方法如下:

npm install --save-dev babel-preset-stage-0

// in .babelrc
{
    "presets": ["stage-0"]
}

舞台更新:

已将其移至stage-3

Babel 7 更新:

根据 Babel 7 的官方声明,阶段预设(stage presets)已被弃用。

使用的 Babel 插件是@babel/plugin-proposal-class-properties

npm i --save-dev @babel/plugin-proposal-class-properties

{
    "plugins": ["@babel/plugin-proposal-class-properties"]
}

注意: 此插件已包含在 @babel/preset-env 中。


29
问题在于常量是可重新赋值的。原作者不希望这样。 - CodingIntrigue
3
知悉,此内容现在处于 Babel 的 stage-2 阶段。 - bmaupin
8
这不是常数。 - Dave L.
2
@CodingIntrigue 调用 Object.freeze() 方法能解决这个问题吗? - Antimony
2
@Antimony 我没有测试过,但我认为应该可以。问题是它会应用于类的所有属性,包括非静态属性。 - CodingIntrigue
显示剩余3条评论

15

这篇文档中,它说:

意图上不存在直接的声明方式来定义原型数据属性(方法以外的其他属性),类属性或实例属性。

这意味着这是故意这样的。

也许你可以在构造函数中定义一个变量?

constructor(){
    this.key = value
}

4
是的,这是可行的。此外,我想提一下,构造函数在实例创建时被调用,对于每个实例而言,this.key将不相同。静态方法和属性允许我们直接从类中使用它们,而无需创建实例。静态方法/属性有其优点和缺点。 - Kirill Husiatyn
5
常量应该是不可变的。在构造期间给对象的属性赋值会产生可以修改的属性。 - philraj

12

你也可以在你的类(es6)/构造函数(es5)对象上使用Object.freeze使其不可变:

class MyConstants {}
MyConstants.staticValue = 3;
MyConstants.staticMethod = function() {
  return 4;
}
Object.freeze(MyConstants);
// after the freeze, any attempts of altering the MyConstants class will have no result
// (either trying to alter, add or delete a property)
MyConstants.staticValue === 3; // true
MyConstants.staticValue = 55; // will have no effect
MyConstants.staticValue === 3; // true

MyConstants.otherStaticValue = "other" // will have no effect
MyConstants.otherStaticValue === undefined // true

delete MyConstants.staticMethod // false
typeof(MyConstants.staticMethod) === "function" // true

试图更改类将会导致软故障(不会抛出任何错误,只是没有任何效果)。


6
对于那些来自其他语言的人来说,“软失败”真的很可怕——我们刚适应了工具在查找错误方面并不太有帮助的想法,现在连运行时也不能帮助我们了。(除此之外,我喜欢你的解决方案。) - Tom
1
我喜欢使用Object.freeze()来强制实现不可变性,并且最近一直在大量使用它。只是不要忘记递归应用它! - jeffwtribble
@Tom,这就是使用严格模式的作用,这也是新的默认设置。这不是Object.freeze的问题,而是松散模式忽略了这个问题。 - Bergi

9
也许可以把所有的常量放在一个冻结对象中?
class MyClass {

    constructor() {
        this.constants = Object.freeze({
            constant1: 33,
            constant2: 2,
        });
    }

    static get constant1() {
        return this.constants.constant1;
    }

    doThisAndThat() {
        //...
        let value = this.constants.constant2;
        //...
    }
}

6
静态函数无法使用变量"this"。 - PokerFace

7

你可以使用ES6类的一个奇特特性来定义类上的静态常量。由于静态变量会被它们的子类继承,所以你可以这样做:

const withConsts = (map, BaseClass = Object) => {
  class ConstClass extends BaseClass { }
  Object.keys(map).forEach(key => {
    Object.defineProperty(ConstClass, key, {
      value: map[key],
      writable : false,
      enumerable : true,
      configurable : false
    });
  });
  return ConstClass;
};

class MyClass extends withConsts({ MY_CONST: 'this is defined' }) {
  foo() {
    console.log(MyClass.MY_CONST);
  }
}

1
这正是OP所要求的,据我所知,在众多答案中,这是唯一正确且完整的答案。干得好。 - Nathan

4

这里还有一种方法可以实现

/*
one more way of declaring constants in a class,
Note - the constants have to be declared after the class is defined
*/
class Auto{
   //other methods
}
Auto.CONSTANT1 = "const1";
Auto.CONSTANT2 = "const2";

console.log(Auto.CONSTANT1)
console.log(Auto.CONSTANT2);

注意 - 顺序很重要,您不能将常量放在上面。
用法
console.log(Auto.CONSTANT1);

13
它们并非不可变的。 - John Harding

4

就像https://stackoverflow.com/users/2784136/rodrigo-botti所说的那样,我认为你正在寻找Object.freeze()。以下是一个带有不可变静态属性的类示例:

class User {
  constructor(username, age) {
    if (age < User.minimumAge) {
      throw new Error('You are too young to be here!');
    }
    this.username = username;
    this.age = age;
    this.state = 'active';
  }
}

User.minimumAge = 16;
User.validStates = ['active', 'inactive', 'archived'];

deepFreeze(User);

function deepFreeze(value) {
  if (typeof value === 'object' && value !== null) {
    Object.freeze(value);
    Object.getOwnPropertyNames(value).forEach(property => {
      deepFreeze(value[property]);
    });
  }
  return value;
}

3
您可以通过冻结类来使“常量”为只读(不可变)。例如:
class Foo {
    static BAR = "bat"; //public static read-only
}

Object.freeze(Foo); 

/*
Uncaught TypeError: Cannot assign to read only property 'BAR' of function 'class Foo {
    static BAR = "bat"; //public static read-only
}'
*/
Foo.BAR = "wut";

1
如果您需要除了使用Object.freeze()不可变属性之外的可变类属性,只需将它们包装到某个可变对象中。例如:不要使用class Cnt { static __cnt=0; get uniq() { return ++Cnt.__cnt } }; Object.freeze(Cnt),而是使用class Cnt { static __var={cnt:0}; get uniq() { return ++Cnt.__var.cnt } }; Object.freeze(Cnt) - Tino

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接