ChatGPT解决这个技术问题 Extra ChatGPT

Declaring static constants in ES6 classes?

I want to implement constants in a class, because that's where it makes sense to locate them in the code.

So far, I have been implementing the following workaround with static methods:

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

I know there is a possibility to fiddle with prototypes, but many recommend against this.

Is there a better way to implement constants in ES6 classes?

Personally I just use uppercase VARNAMES, and tell myself to not touch them ;)
@twicejr I think this is not the same, for static variables can be accessed without first instantiating an object of that class?

C
CodingIntrigue

Here's a few things you could do:

Export a const from the module. Depending on your use case, you could just:

export const constant1 = 33;

And import that from the module where necessary. Or, building on your static method idea, you could declare a static get accessor:

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

  static get constant1() {
    return constant1;
  }

  static get constant2() {
    return constant2;
  }
}

That way, you won't need parenthesis:

const one = Example.constant1;

Babel REPL Example

Then, as you say, since a class is just syntactic sugar for a function you can just add a non-writable property like so:

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

It may be nice if we could just do something like:

class Example {
    static const constant1 = 33;
}

But unfortunately this class property syntax is only in an ES7 proposal, and even then it won't allow for adding const to the property.


is there any confirmation that static properties get computed once for things like this, or is it safer to use IIFE and add the property manually in the IIFE to avoid repeated construction of return values. I'm worried that if the result of the getter is really heavy, like a 100000 entry JSObject, then the poor getter will have to construct it each time the getter is called. Its easy to test by performance.now/date diff, but it might be implemented differently, its certaintly easier to implement getters as literal evaluation rather than advanced decisions whether its constant or not.
while the above cleverly adds a constant property to a class, the actual value for the constant is "outside" the class definition "{}", which really violates one of the definitions of encapsulation. I guess it is sufficient to just define a constant property "inside" the class and there is no need for the get in this case.
@NoChance Good points. That was just illustrative. There's no reason the getter method couldn't fully encapsulate the value if required.
Looking forward to use the ES7 proposal because it looks to me more natural and equivalent to the the majority of OO languages.
the super keyword is a little bit more than syntactic sugar
B
Benny Jobigan
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

Seems to work for me.


is this accessible inside the class in a normal method?
@PirateApp you can access it anywhere as a static method, even from inside an instance of the class. However, since it's static you can't use this.MyConst from inside a Whatever instance, you always have to write it like this: Whatever.MyConst
or this.constructor.MyConst
Static getters is probably the cleanest solution for now.
I've done this too but it fundamentally conflicts with the definition of "static" since the returned value is not shared between instances. It's convenient but ultimately a poor choice
b
borracciaBlu

I'm using babel and the following syntax is working for me:

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

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

Please consider that you need the preset "stage-0".
To install it:

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

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

Update:

currently use stage-3


Problem is that constant is reassignable. Op doesn't want that
FYI, this is now in babel stage-2
those aren't constants
@CodingIntrigue Would calling Object.freeze() on the class fix that?
@Antimony I haven't tested that but I would think so. The problem is it would apply to all properties of the class. Non-static too.
s
sdgluck

In this document it states:

There is (intentionally) no direct declarative way to define either prototype data properties (other than methods) class properties, or instance property

This means that it is intentionally like this.

Maybe you can define a variable in the constructor?

constructor(){
    this.key = value
}

Yes, this can work. Also, I want to mention, that constructor invokes when instance created and for each instance this.key will be not the same. Static method and properties allow us to use them directly from class, without creating instance. There are good and weak points of static methods / properties.
Constants should be immutable. Assigning to properties on the object during construction will yield properties that can be modified.
r
rodrigo.botti

It is also possible to use Object.freeze on you class(es6)/constructor function(es5) object to make it immutable:

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

Trying to alter the class will give you a soft-fail (won't throw any errors, it will simply have no effect).


That soft-fail is pretty scary for those of us coming from other languages - just adapting to the idea that the tools don't help us much in finding errors, now even the runtime won't help. (Otherwise I like your solution.)
I love Object.freeze() for enforcing immutability, and have been using it a lot lately. Just don't forget to apply it recursively!
a
aRIEL

Maybe just put all your constants in a frozen object?

class MyClass {

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

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

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

The static function can't use the variable 'this'.
T
TbWill4321

You can create a way to define static constants on a class using an odd feature of ES6 classes. Since statics are inherited by their subclasses, you can do the following:

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);
  }
}

This is exactly what the OP asked for, and as far as I can tell, the only correct and complete answer in the entire list of many answers. Well done.
C
Community

Like https://stackoverflow.com/users/2784136/rodrigo-botti said, I think you're looking for Object.freeze(). Here's an example of a class with immutable statics:

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;
}

n
ncmathsadist

I did this.

class Circle
{
    constuctor(radius)
    {
        this.radius = radius;
    }
    static get PI()
    {
        return 3.14159;
    }
}

The value of PI is protected from being changed since it is a value being returned from a function. You can access it via Circle.PI. Any attempt to assign to it is simply dropped on the floor in a manner similar to an attempt to assign to a string character via [].


F
Fraser

You can make the "constants" read-only (immutable) by freezing the class. e.g.

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";

If you need mutable class properties besides immutable ones with Object.freeze(), just wrap them into some mutable object. Example: Instead of class Cnt { static __cnt=0; get uniq() { return ++Cnt.__cnt } }; Object.freeze(Cnt) do class Cnt { static __var={cnt:0}; get uniq() { return ++Cnt.__var.cnt } }; Object.freeze(Cnt)
P
Pang

Here is one more way you can do

/* 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);

Note - the Order is important, you cannot have the constants above

Usage

console.log(Auto.CONSTANT1);

They aren't immutable though
V
Vincent

You could use import * as syntax. Although not a class, they are real const variables.

Constants.js

export const factor = 3;
export const pi = 3.141592;

index.js

import * as Constants from 'Constants.js'
console.log( Constants.factor );

d
divil

The cleanest way I've found of doing this is with TypeScript - see How to implement class constants?

class MyClass {
    static readonly CONST1: string = "one";
    static readonly CONST2: string = "two";
    static readonly CONST3: string = "three";
}

Sorry, downvoted, as there this is no runtime protection. For example MyClass['CO'+'NST1']='bug' still changes the constants, even in Typescript! readonly is only compile time sugar, as the Typescript compiler cannot magically create immutable class properties out of nothing. So the compiler neither protects against anything it does not grok nor protects the runtime from accidentally changing things. Even worse: You might think you are protected but aren't! (YMMV, the tested Typescript compiler from Ubuntu 20.04 apparently does not use Object.freeze())
u
user3715912

If trying to make a const/variable static to a class; try using the hash (#) to define a place holder, than a function to access it.

class Region {
    // initially empty, not accessible from outside
    static #empty_region = null; 

    /* 
        Make it visible to the outside and unchangeable 
        [note] created on first call to getter.
    */

    static EMPTY() {
        if (!this.#empty_region)
            this.#empty_region = new Region(0, 0, 0, 0);
        return this.#empty_region;
    }

    #reg = {x0:0, y0:0, x1:0, y1:0};

    constructor(x0, y0, x1, y1) { 
        this.setRegion(x0, y0, x1, y1);
    }

    // setters/getters
}

Implementation:

let someRegion = Region.EMPTY();

let anotherRegion = Region.EMPTY();

D
Dan S

Just declare your variables as private and use a get method to retrieve them.

class MyClass {

   #myConst = 'Something';

   static #anotherConst = 'Something Else';

   get myConst() {
      return this.#myConst; // instance method
   }

   static get anotherConst() {
      return MyClass.#anotherConst; // static method
   }
}

let myClass = new MyClass();

console.log( myClass.myConst + ' is not ' + MyClass.anotherConst );

Users cannot change the original variable, and you can write the class to use the get methods rather than the private variables themselves.


C
Cam Cairns

If you are comfortable mixing and matching between function and class syntax you can declare constants after the class (the constants are 'lifted') . Note that Visual Studio Code will struggle to auto-format the mixed syntax, (though it works).

class MyClass { // ... } MyClass.prototype.consts = { constant1: 33, constant2: 32 }; mc = new MyClass(); console.log(mc.consts.constant2);


I
Inanc Cakil

Adding up to other answers you need to export the class to use in a different class. This is a typescript version of it.

//Constants.tsx const DEBUG: boolean = true; export class Constants { static get DEBUG(): boolean { return DEBUG; } } //Anotherclass.tsx import { Constants } from "Constants"; if (Constants.DEBUG) { console.log("debug mode") }


S
Sergey Sahakyan

Here You Go!

const Status = Object.freeze(class Status {
  static Disabled = 0
  static Live = 1
})