404

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?

sdgluck
  • 24,894
  • 8
  • 75
  • 90
Jérôme Verstrynge
  • 57,710
  • 92
  • 283
  • 453
  • 12
    Personally I just use uppercase VARNAMES, and tell myself to not touch them ;) – twicejr Jul 29 '16 at 12:18
  • 3
    @twicejr I think this is not the same, for static variables can be accessed without first instantiating an object of that class? – Lucas Feb 07 '17 at 20:18

20 Answers20

487

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.

CodingIntrigue
  • 75,930
  • 30
  • 170
  • 176
  • 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. – Dmytro Aug 16 '17 at 13:35
  • 6
    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 Oct 05 '17 at 08:43
  • 1
    @NoChance Good points. That was just illustrative. There's no reason the getter method couldn't fully encapsulate the value if required. – CodingIntrigue Oct 05 '17 at 08:47
  • 1
    Looking forward to use the ES7 proposal because it looks to me more natural and equivalent to the the majority of OO languages. – Sangimed Apr 13 '18 at 08:50
  • the `super` keyword is a little bit more than syntactic sugar – Rivenfall Mar 25 '19 at 12:55
  • Is the class property syntax supported now? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields – qwr Aug 18 '21 at 21:57
  • [this](https://stackoverflow.com/a/50270816/1045881) is a better answer – toddmo Nov 17 '22 at 13:41
80
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

Seems to work for me.

Benny Jobigan
  • 5,078
  • 2
  • 31
  • 41
  • is this accessible inside the class in a normal method? – PirateApp Apr 21 '19 at 04:52
  • 4
    @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` – Chunky Chunk Apr 28 '19 at 16:55
  • or this.constructor.MyConst – Martijn Scheffer Oct 13 '20 at 17:38
  • 1
    Static getters is probably the cleanest solution for now. – qwr Aug 18 '21 at 22:04
  • 2
    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 – Madbreaks Oct 07 '21 at 18:57
  • 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 :) @Madbreaks 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. – jave.web Apr 07 '22 at 00:07
  • 2
    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 @jave.web mentioned. – jbuhacoff Aug 21 '22 at 05:11
  • ` Object.defineProperty` allowed me to achieve something similar to java constants that I wanted. Nice. – FtheBuilder Feb 13 '23 at 01:21
21

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

borracciaBlu
  • 4,017
  • 3
  • 33
  • 41
  • 29
    Problem is that constant is reassignable. Op doesn't want that – CodingIntrigue Aug 27 '16 at 20:29
  • hi @Boyang do you have `stage-0` installed and in `.babelrc`? – borracciaBlu Sep 12 '16 at 01:18
  • 3
    FYI, this is now in babel `stage-2` – bmaupin Aug 06 '17 at 19:48
  • 8
    those aren't constants – Dave L. Aug 15 '17 at 19:09
  • 2
    @CodingIntrigue Would calling `Object.freeze()` on the class fix that? – Antimony Sep 12 '17 at 02:07
  • 2
    @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. – CodingIntrigue Sep 12 '17 at 05:51
  • 1
    This is now `stage-3` – Sukima Aug 22 '19 at 19:23
  • @CodingIntrigue 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)` – Tino Jul 29 '21 at 11:44
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
}
sdgluck
  • 24,894
  • 8
  • 75
  • 90
DevAlien
  • 2,456
  • 15
  • 17
  • 4
    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. – Kirill Husiatyn Feb 23 '17 at 14:08
  • 5
    Constants should be immutable. Assigning to properties on the object during construction will yield properties that can be modified. – philraj Jun 13 '18 at 19:10
12

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

rodrigo.botti
  • 268
  • 1
  • 4
  • 9
  • 6
    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.) – Tom Aug 02 '16 at 21:01
  • 1
    I love `Object.freeze()` for enforcing immutability, and have been using it a lot lately. Just don't forget to apply it recursively! – jeffwtribble Mar 06 '17 at 21:47
  • @Tom That's what [using strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/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. – Bergi Feb 09 '23 at 18:34
9

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;
        //...
    }
}
aRIEL
  • 167
  • 1
  • 3
7

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);
  }
}
TbWill4321
  • 8,626
  • 3
  • 27
  • 25
  • 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. – Nathan Sep 17 '21 at 12:52
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;
}
Community
  • 1
  • 1
jeffwtribble
  • 1,131
  • 1
  • 9
  • 6
4

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);
Pang
  • 9,564
  • 146
  • 81
  • 122
user3871424
  • 244
  • 4
  • 4
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 [].

ncmathsadist
  • 4,684
  • 3
  • 29
  • 45
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 );
Vincent
  • 3,191
  • 3
  • 29
  • 35
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";
Fraser
  • 15,275
  • 8
  • 53
  • 104
  • 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)` – Tino Jul 29 '21 at 11:45
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";
}
divil
  • 144
  • 6
  • 2
    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()`) – Tino Jul 29 '21 at 12:11
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.

Dan S
  • 99
  • 6
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.

rjha94
  • 4,292
  • 3
  • 30
  • 37
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);    
Cam Cairns
  • 406
  • 4
  • 5
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")
}
Inanc Cakil
  • 306
  • 2
  • 9
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();
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);
fuweichin
  • 1,398
  • 13
  • 14
  • You probably should attach them to either the class (constructor) or to the prototype, not both – Bergi May 15 '23 at 17:13
  • The above examples just followed convention of browser native class constants. I mean **to keep consistency with native class**, especially for polyfill/shim classes. – fuweichin May 15 '23 at 17:37
  • Ah, that makes sense, but notice that the DOM constants inherit this pattern from Java, it's not really idiomatic JavaScript. – Bergi May 15 '23 at 18:32
-1

Here You Go!

const Status = Object.freeze(class Status {
  static Disabled = 0
  static Live = 1
})
Sergey Sahakyan
  • 723
  • 8
  • 15