Declaring static constants in ES6 classes?
Asked Answered
A

20

418

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?

Andria answered 18/9, 2015 at 8:22 Comment(3)
Personally I just use uppercase VARNAMES, and tell myself to not touch them ;)Adiaphorous
@Adiaphorous I think this is not the same, for static variables can be accessed without first instantiating an object of that class?Capo
that @Adiaphorous said. It is a convention in many programming languages that constants are written in capitals. So I also use, inside my class: static MY_CONSTANT = 'ABC';Leventis
K
499

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.

Kraul answered 18/9, 2015 at 8:41 Comment(8)
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.Richelieu
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.Percheron
@Percheron Good points. That was just illustrative. There's no reason the getter method couldn't fully encapsulate the value if required.Kraul
Looking forward to use the ES7 proposal because it looks to me more natural and equivalent to the the majority of OO languages.Bemoan
the super keyword is a little bit more than syntactic sugarBrevier
Is the class property syntax supported now? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Drive
this is a better answerSusurrant
Almost 10 years old answer, time for an update? :)Ursola
J
87
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

Seems to work for me.

Joint answered 10/5, 2018 at 10:30 Comment(8)
is this accessible inside the class in a normal method?Evania
@Evania 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.MyConstInteroceptor
or this.constructor.MyConstRidglee
Static getters is probably the cleanest solution for now.Drive
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 choiceThy
Using this for "constant" is a hack: The property could still be set if you'd define setter. Even if you don't define setter, this code will still be valid Whatever.MyConst = "aa" it will NOT store the "aa" into that "constant", but also won't throw any error :) @Thy you could define const above the class and return it in the getter that I think is the closest thing to a shared one, although I agree that's a bit singleton-ish.Indrawn
If you want it to look and act like a constant, you probably want to also define the corresponding setter like static set MyConst(value) { throw new Error('cannot redefine constant'); }, to avoid the silent error @Indrawn mentioned.Naphthol
` Object.defineProperty` allowed me to achieve something similar to java constants that I wanted. Nice.Staurolite
P
22

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 for stage:

it was moved on stage-3.

Update Babel 7:

As per Babel 7 stage presets are deprecated.

The Babel plugin to use is @babel/plugin-proposal-class-properties.

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

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

Note: This plugin is included in @babel/preset-env

Padegs answered 24/8, 2016 at 6:9 Comment(8)
Problem is that constant is reassignable. Op doesn't want thatKraul
hi @Boyang do you have stage-0 installed and in .babelrc?Padegs
FYI, this is now in babel stage-2Kordofan
those aren't constantsXenocryst
@Kraul Would calling Object.freeze() on the class fix that?Antiparticle
@Antiparticle 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.Kraul
This is now stage-3Mentor
@Kraul AFAICS instance members stay mutable, so only the class properties (static members) are affected by Object.freeze(). If you need mutable class properties besides immutable ones after the 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)Overindulge
F
15

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
}
Furniture answered 18/9, 2015 at 8:33 Comment(2)
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.Cyrillus
Constants should be immutable. Assigning to properties on the object during construction will yield properties that can be modified.Hermon
H
13

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).

Hatshepsut answered 16/5, 2016 at 19:11 Comment(3)
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.)Shingle
I love Object.freeze() for enforcing immutability, and have been using it a lot lately. Just don't forget to apply it recursively!Sartorius
@Shingle That's what using strict mode is for, which is the new default anyway. It's not a problem with Object.freeze, rather it's sloppy mode ignoring the problem.Pesade
A
10

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;
        //...
    }
}
Altamirano answered 5/9, 2018 at 10:34 Comment(2)
The static function can't use the variable 'this'.Fabrienne
@Fabrienne under reasonable conditions I believe this in a static method can be counted on to refer to the class itself. For example a static factory method might return new this(). But I think you're right; the code above seems confused about whether this.constants is an instance property or a class property.Traci
S
8

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);
  }
}
Sloop answered 23/4, 2018 at 18:16 Comment(1)
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.Scherzando
S
4

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;
}
Sartorius answered 6/3, 2017 at 22:3 Comment(0)
A
3

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);
Angle answered 1/9, 2017 at 18:49 Comment(2)
They aren't immutable thoughFoist
This answer is misleading. CONSTANT1 and CONSTANT2 are not constants but variables because their values can be changed. "Constants" by definition must be constant, i.e. immutable.Liveried
S
3

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 [].

Swearword answered 31/1, 2020 at 14:32 Comment(0)
V
3

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 );
Valenba answered 23/3, 2020 at 19:37 Comment(0)
S
3

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";
Saez answered 29/3, 2020 at 12:15 Comment(1)
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)Overindulge
H
3

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";
}
Holyhead answered 24/12, 2020 at 16:43 Comment(1)
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())Overindulge
U
1

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.

Underpin answered 17/5, 2022 at 17:28 Comment(0)
B
1

One pattern that I use to expose error codes, i.e.,

  • I have many constants inside the module

  • I may not want to expose all constants to callers

  • I do not want to provide 1 static constant for one exposed constant

     // inside the module 
     const Errors = {
    
         INTERNAL: 100,
         EMPTY_QUEUE: 101,
         UNKNOWN_COMMAND: 102,
         OK: 200, 
         MOVE: 201,
         CREATE_DOT: 202,
         PIXEL_MAPPING: 203
    
     }
    
     Object.freeze(Errors);
    
     class  PlotterError extends Error {
       // use constant inside the module 
       code = Errors.INTERNAL;
    
       constructor(message, code) {
         super(message);
         this.name = 'PlotterError';
         this.code = code 
       }
    
     }
    
      // expose via static constant 
      Class Plotter {
       .....
       static get ERRORS() {
         return Errors;
        }
       ....
       export Plotter;
       // module ends 
    
    
       // in the caller 
       import {Plotter} from ... 
        try {
    
         this.plotter.execute();
    
        } catch(error) {
    
         if(error.code == Plotter.ERRORS.EMPTY_QUEUE) {
         // 
        }
      }
    

We can also decide to expose only the constants we want by breaking the constants acr two objects.

Babylonian answered 9/2, 2023 at 16:6 Comment(0)
A
0

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);    
Alibi answered 22/12, 2019 at 17:16 Comment(0)
P
0

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")
}
Plaintive answered 24/10, 2020 at 18:13 Comment(0)
E
0

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();
Eason answered 25/10, 2021 at 22:44 Comment(0)
I
0

In order to keep consistency with native class, consider ECMAScript proposal Decorators to declare static constants, but before that day comes, use a helper function instead:

function toConstantDescriptors(entries){
  let arr = Array.isArray(entries) ? entries:
    Object.entries(entries);
  let descriptors = {};
  for(let [name, value] of arr) {
    descriptors[name] = {value, writable: false, enumerable: true, configurable: false};
  }
  return descriptors;
}

example usage 1

// define an "enum" of constants
const ReadyState = {
  CONNECTING: 0,
  OPEN: 1,
  CLOSED: 2,
};
export class ServerSentEvents extends EventTarget {
  // ...
  #readyState;
  #connect(){
    this.#readyState = ReadyState.CONNECTING; // internal use of constants
    // ...
  }
  get readyState(){
    return this.#readyState;
  }
}
// attach constants to a class
let descriptors = toConstantDescriptors(ReadyState);
Object.defineProperties(ServerSentEvents, descriptors);
Object.defineProperties(ServerSentEvents.prototype, descriptors);

example usage 2

// define constants
const CONNECTING = 0;
const OPEN = 1;
const CLOSED = 2;

export class ServerSentEvents extends EventTarget {
  // ...
  #readyState;
  #connect(){
    this.#readyState = CONNECTING; // internal use of constants
    // ...
  }
  get readyState(){
    return this.#readyState;
  }
}
// attach constants to a class
let descriptors = toConstantDescriptors({CONNECTING, OPEN, CLOSED});
Object.defineProperties(ServerSentEvents, descriptors);
Object.defineProperties(ServerSentEvents.prototype, descriptors);
Isaacson answered 15/5, 2023 at 17:6 Comment(3)
You probably should attach them to either the class (constructor) or to the prototype, not bothPesade
The above examples just followed convention of browser native class constants. I mean to keep consistency with native class, especially for polyfill/shim classes.Isaacson
Ah, that makes sense, but notice that the DOM constants inherit this pattern from Java, it's not really idiomatic JavaScript.Pesade
P
-1

Here You Go!

const Status = Object.freeze(class Status {
  static Disabled = 0
  static Live = 1
})
Presidentship answered 21/9, 2021 at 18:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.