211

I'm rebuilding an old Java project in Javascript, and realized that there's no good way to do enums in JS.

The best I can come up with is:

const Colors = {
    RED: Symbol("red"),
    BLUE: Symbol("blue"),
    GREEN: Symbol("green")
};
Object.freeze(Colors);

The const keeps Colors from being reassigned, and freezing it prevents mutating the keys and values. I'm using Symbols so that Colors.RED is not equal to 0, or anything else besides itself.

Is there a problem with this formulation? Is there a better way?


(I know this question is a bit of a repeat, but all the previous Q/As are quite old, and ES6 gives us some new capabilities.)


EDIT:

Another solution, which deals with the serialization problem, but I believe still has realm issues:

const enumValue = (name) => Object.freeze({toString: () => name});

const Colors = Object.freeze({
    RED: enumValue("Colors.RED"),
    BLUE: enumValue("Colors.BLUE"),
    GREEN: enumValue("Colors.GREEN")
});

By using object references as the values, you get the same collision-avoidance as Symbols.

cypherfunc
  • 2,251
  • 2
  • 12
  • 14
  • 2
    this would be a perfect approach in es6 . You dont have to freeze it – NiRUS Jun 09 '17 at 01:35
  • 2
    @Nirus you do, if you don't want it to be modified. – zerkms Jun 09 '17 at 01:37
  • 2
    Did you notice [this answer](https://stackoverflow.com/a/30058506/1048572)? – Bergi Jun 09 '17 at 01:47
  • Is there some reason why mimicking the way TypeScript compiles enums would not work? –  Jun 09 '17 at 02:05
  • 4
    One issue I can think of: Can't use this enum with `JSON.stringify()`. Can't serialize / deserialize `Symbol`. – le_m Jun 09 '17 at 12:27
  • Another issue with this is that this Symbol.for() inserts into a global symbol table. So if you have two enums with the same symbol name, you will get a potentially difficult-to-diagnose bug if you actually try to do a lookup with Symbol. I'm somewhat new to JavaScript, but to me, even with ES6, the language seems to me to need some sort of syntatic sugar for enums similar to Java5 enums. – DaBlick May 14 '18 at 14:39
  • @DaBlick I'm not using `Symbol.for` in my example here, for exactly that reason. Did you mean to respond to Bergi's cross-realm comments below? – cypherfunc May 14 '18 at 18:21
  • 1
    @ErictheRed I've been using string enum constant values for years without any hassles, because using Flow (or TypeScript) guarantees way more type safety than fretting about collision avoidance ever will – Andy Dec 19 '18 at 02:21
  • @Andy I agree, string constants are the practical way to go. However, I think that guaranteed safety can be useful in a "loose" language like JS, if it doesn't require large amounts of overhead. – cypherfunc Jan 03 '19 at 15:52
  • `so that Colors.RED is not equal to 0` why? IMHO this is a useless thing for enums. Even in Java-land enums can be converted to numbers because more often than not you will need to store the enums in a file or database – slebetman May 09 '21 at 22:48

18 Answers18

204

Is there a problem with this formulation?

I don't see any.

Is there a better way?

I'd collapse the two statements into one:

const Colors = Object.freeze({
    RED:   Symbol("red"),
    BLUE:  Symbol("blue"),
    GREEN: Symbol("green")
});

If you don't like the boilerplate, like the repeated Symbol calls, you can of course also write a helper function makeEnum that creates the same thing from a list of names.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 4
    Aren't there realm issues here? –  Jun 09 '17 at 03:28
  • 3
    @torazaburo You mean, when the code is loaded twice it will generate different symbols, which would not be an issue with strings? Yeah, good point, make it an answer :-) – Bergi Jun 09 '17 at 03:44
  • I guess you could store the Symbols somewhere, and then assign them when you initialize the Enum. Is there another way to make them unique? (`Symbol.for` would have the same issue.) – cypherfunc Jun 13 '17 at 00:02
  • 2
    @ErictheRed No, `Symbol.for` does *not* have cross-realm issues, however it does have the usual collision problem with a [truly global namespace](https://stackoverflow.com/a/30985314/1048572). – Bergi Jun 13 '17 at 00:11
  • I guess I'm not understanding what you mean by realm issues. I believe ```Symbol.for``` creates a new Symbol, and registers it globally so any following call to ```Symbol.for``` with the same input will return the same Symbol. However, I don't believe there's any guarantee that the next time you run the code, it will create the same Symbol for that input when it initializes. – cypherfunc Jun 23 '17 at 05:20
  • 1
    @ErictheRed It does indeed guarantee to create the exact same symbol regardless when and where (from which realm/frame/tab/process) it is called – Bergi Jun 23 '17 at 05:34
  • What is a usage of this? You can't coerce the symbol into a string, so I am at a loss of one would use the above example (assuming a need for color enums). because, don't you want the value of the object key OR are you freezing the "key" proper? – james emanon Jul 22 '18 at 00:54
  • 1
    @jamesemanon You can [get the description if you want](https://stackoverflow.com/q/30301728/1048572), but I would use it primarily for debugging only. Rather have a custom enum-to-string conversion function as usual (something along the lines `enum => ({[Colors.RED]: "bright red", [Colors.BLUE]: "deep blue", [Colors.GREEN]: "grass green"}[enum])`). – Bergi Jul 22 '18 at 12:26
  • @jamesemanon I don't understand what you mean by "*are you freezing the "key" proper?*". With symbols and `Object.freeze`, the `Colors` object is totally immutable. – Bergi Jul 22 '18 at 12:27
  • What about default value? – Sky Feb 12 '19 at 10:29
  • @Sky What kind of default are you referring to, you mean when lookup in `Colors` fails? – Bergi Feb 12 '19 at 11:59
  • @Bergi Yes exactly. – Sky Feb 12 '19 at 14:32
  • 2
    @Sky A default value for the lookup in `Colors` has nothing to do with the enum definition. One would do that as usual, with `Colors[name] || Colors.BLUE` or `Colors.hasOwnProperty(name) ? Colors[name] : Colors.BLUE`. – Bergi Feb 12 '19 at 17:02
  • VS Code told me that `enum` is a reserved word in a .js file. Does that mean JS now has enums? – Zach Smith Jan 20 '23 at 12:48
  • @ZachSmith No, it's just a reserved word that you cannot use as an identifier. – Bergi Jan 20 '23 at 12:50
  • Why would a word that has no meaning be reserved? – Zach Smith Jan 21 '23 at 14:44
  • 2
    @ZachSmith So that a meaning can be given to it in future versions of the language without breaking existing code. – Bergi Jan 21 '23 at 14:45
  • How about something like this: `const Colors = Object.freeze({ RED: {}, BLUE: {}, GREEN: {} });` ? What are the pros and cons of using objects instead of `Symbol`? – popov895 Jul 01 '23 at 08:28
  • @popov895 symbols have the advantage of being usable as property names and that you cannot accidentally coerce them to a string – Bergi Jul 01 '23 at 12:59
41

Whilst using Symbol as the enum value works fine for simple use cases, it can be handy to give properties to enums. This can be done by using an Object as the enum value containing the properties.

For example we can give each of the Colors a name and hex value:

/**
 * Enum for common colors.
 * @readonly
 * @enum {{name: string, hex: string}}
 */
const Colors = Object.freeze({
  RED:   { name: "red", hex: "#f00" },
  BLUE:  { name: "blue", hex: "#00f" },
  GREEN: { name: "green", hex: "#0f0" }
});

Including properties in the enum avoids having to write switch statements (and possibly forgetting new cases to the switch statements when an enum is extended). The example also shows the enum properties and types documented with the JSDoc enum annotation.

Equality works as expected with Colors.RED === Colors.RED being true, and Colors.RED === Colors.BLUE being false.

gp.
  • 8,074
  • 3
  • 38
  • 39
Justin Emery
  • 1,644
  • 18
  • 25
  • 3
    Note that Object.freeze is not deep, e.g. you could change Colors.RED.name = "charlie"; You could use something like this to freeze nested objects: const deepFreeze = obj => { Object.keys(obj).forEach(prop => { if (typeof obj[prop] === 'object') deepFreeze(obj[prop]); }); return Object.freeze(obj); }; – Stonetip Jul 05 '22 at 19:37
13

This is my personal approach.

class ColorType {
    static get RED () {
        return "red";
    }

    static get GREEN () {
        return "green";
    }

    static get BLUE () {
        return "blue";
    }
}

// Use case.
const color = Color.create(ColorType.RED);
  • 3
    I wouldn't recommend using this as it provides no way to iterate over all possible values, and no way to check if a value is a ColorType without manually checking for each. – Domino Apr 06 '20 at 07:10
  • 1
    I am afraid this is too much code for defining an Enum type, which should be very concise – Zhe Jan 05 '22 at 15:16
13

Update 11.05.2020:
Modified to include static fields and methods to closer replicate "true" enum behavior.

If you're planning on updating I would recommend trying to use what I call an "Enum Class" (barring any browser or runtime env limitations you can't accept). It's basically a very simple and clean class that uses private fields and limited accessors to simulate the behavior of an enum. This is something I sometimes do in C# when I want to build more functionality into an enum.

I realize private class fields are still experimental at this point but it seems to work for the purposes of creating a class with immutable fields/properties. Browser support is decent as well. The only "major" browsers that don't support it are Firefox (which I'm sure they will soon) and IE (who cares).

DISCLAIMER:
I am not a developer. I just put this together to solve the limitations of nonexistent enums in JS when I was working on a personal project.

Sample Class

class Colors {
    // Private Fields
    static #_RED = 0;
    static #_GREEN = 1;
    static #_BLUE = 2;

    // Accessors for "get" functions only (no "set" functions)
    static get RED() { return this.#_RED; }
    static get GREEN() { return this.#_GREEN; }
    static get BLUE() { return this.#_BLUE; }
}

You should now be able to call your enums directly.

Colors.RED; // 0
Colors.GREEN; // 1
Colors.BLUE; // 2

The combination of using private fields and limited accessors means that the existing enum values are well protected (they're essentially constants now).

Colors.RED = 10 // Colors.RED is still 0
Colors._RED = 10 // Colors.RED is still 0
Colors.#_RED = 10 // Colors.RED is still 0
dsanchez
  • 1,038
  • 9
  • 9
11

As mentioned above, you could also write a makeEnum() helper function:

function makeEnum(arr){
    let obj = Object.create(null);
    for (let val of arr){
        obj[val] = Symbol(val);
    }
    return Object.freeze(obj);
}

Use it like this:

const Colors = makeEnum(["red","green","blue"]);
let startColor = Colors.red; 
console.log(startColor); // Symbol(red)

if(startColor == Colors.red){
    console.log("Do red things");
}else{
    console.log("Do non-red things");
}
tonethar
  • 2,112
  • 26
  • 33
  • 2
    As a one-liner: `const makeEnum = (...lst) => Object.freeze(Object.assign({}, ...lst.map(k => ({[k]: Symbol(k)}))));` Then use it as `const colors = makeEnum("Red", "Green", "Blue")` – Manuel Ebert Mar 08 '19 at 00:51
  • 1
    I would do a tiny change: `let obj = Object.create(null)` to make the object without any properties. – some Oct 25 '22 at 15:36
  • Good suggestion - made that change – tonethar Oct 26 '22 at 15:26
8

If you don't need pure ES6 and can use Typescript, it has a nice enum:

https://www.typescriptlang.org/docs/handbook/enums.html

Chris Halcrow
  • 28,994
  • 18
  • 176
  • 206
7

Check how TypeScript does it. Basically they do the following:

const MAP = {};

MAP[MAP[1] = 'A'] = 1;
MAP[MAP[2] = 'B'] = 2;

MAP['A'] // 1
MAP[1] // A

Use symbols, freeze object, whatever you want.

givehug
  • 1,863
  • 1
  • 14
  • 19
  • I'm not following why it uses ```MAP[MAP[1] = 'A'] = 1;``` instead of ```MAP[1] = 'A'; MAP['A'] = 1;```. I've always heard that using an assignment as an expression is bad style. Also, what benefit do you get from the mirrored assignments? – cypherfunc Apr 27 '18 at 17:05
  • 1
    Here is a link to how enum mapping is compiled to es5 in their docs. https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings I can image it would simply be easier and more concise to compile it to single line eg `MAP[MAP[1] = 'A'] = 1;`. – givehug May 02 '18 at 09:21
  • Huh. So it looks like the mirroring just makes it easy to switch between the string and number/symbol representations of each value, and check that some string or number/symbol ```x``` is a valid Enum value by doing ```Enum[Enum[x]] === x```. It doesn't solve any of my original issues, but could be useful and doesn't break anything. – cypherfunc May 02 '18 at 16:13
  • 1
    Keep in mind that TypeScript adds a layer of robustness which is lost once the TS code is compiled. If your entire app is written in TS it's great, but if you want JS code to be robust, the frozen map of symbols sounds like a safer pattern. – Domino Apr 06 '20 at 07:08
5

You can check Enumify, a very good and well featured library for ES6 enums.

Bernhard
  • 4,855
  • 5
  • 39
  • 70
Emmanuel.B
  • 361
  • 3
  • 9
3

Here is my implementation of a Java enumeration in JavaScript.

I also included unit tests.

const main = () => {
  mocha.setup('bdd')
  chai.should()

  describe('Test Color [From Array]', function() {
    let Color = new Enum('RED', 'BLUE', 'GREEN')
    
    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      chai.assert.isNotNull(Color.RED)
    })

    it('Test: Color.BLUE', () => {
      chai.assert.isNotNull(Color.BLUE)
    })

    it('Test: Color.GREEN', () => {
      chai.assert.isNotNull(Color.GREEN)
    })

    it('Test: Color.YELLOW', () => {
      chai.assert.isUndefined(Color.YELLOW)
    })
  })

  describe('Test Color [From Object]', function() {
    let Color = new Enum({
      RED   : { hex: '#F00' },
      BLUE  : { hex: '#0F0' },
      GREEN : { hex: '#00F' }
    })

    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      let red = Color.RED
      chai.assert.isNotNull(red)
      red.getHex().should.equal('#F00')
    })

    it('Test: Color.BLUE', () => {
      let blue = Color.BLUE
      chai.assert.isNotNull(blue)
      blue.getHex().should.equal('#0F0')
    })

    it('Test: Color.GREEN', () => {
      let green = Color.GREEN
      chai.assert.isNotNull(green)
      green.getHex().should.equal('#00F')
    })

    it('Test: Color.YELLOW', () => {
      let yellow = Color.YELLOW
      chai.assert.isUndefined(yellow)
    })
  })

  mocha.run()
}

class Enum {
  constructor(values) {
    this.__values = []
    let isObject = arguments.length === 1
    let args = isObject ? Object.keys(values) : [...arguments]
    args.forEach((name, index) => {
      this.__createValue(name, isObject ? values[name] : null, index)
    })
    Object.freeze(this)
  }

  values() {
    return this.__values
  }

  /* @private */
  __createValue(name, props, index) {
    let value = new Object()
    value.__defineGetter__('name', function() {
      return Symbol(name)
    })
    value.__defineGetter__('ordinal', function() {
      return index
    })
    if (props) {
      Object.keys(props).forEach(prop => {
        value.__defineGetter__(prop, function() {
          return props[prop]
        })
        value.__proto__['get' + this.__capitalize(prop)] = function() {
          return this[prop]
        }
      })
    }
    Object.defineProperty(this, name, {
      value: Object.freeze(value),
      writable: false
    })
    this.__values.push(this[name])
  }

  /* @private */
  __capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
  }
}

main()
.as-console-wrapper { top: 0; max-height: 100% !important; }
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.2.0/chai.js"></script>
<!--

public enum Color {
  RED("#F00"),
  BLUE("#0F0"),
  GREEN("#00F");
  
  private String hex;
  public String getHex()  { return this.hex;  }
  
  private Color(String hex) {
    this.hex = hex;
  }
}

-->
<div id="mocha"></div>

Update

Here is a more up-to-date version that satisfies MDN.

The Object.prototype.__defineGetter__ has been replaced by Object.defineProperty per MDN's recomendation:

This feature is deprecated in favor of defining getters using the object initializer syntax or the Object.defineProperty() API. While this feature is widely implemented, it is only described in the ECMAScript specification because of legacy usage. This method should not be used since better alternatives exist.

Edit: Added a prototype (Enum.__prototype) for the enum values to handle JSON serialization of the props.

const main = () => {
  mocha.setup('bdd')
  chai.should()

  describe('Test Color [From Array]', function() {
    let Color = new Enum('RED', 'BLUE', 'GREEN')

    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      chai.assert.isNotNull(Color.RED)
    })

    it('Test: Color.BLUE', () => {
      chai.assert.isNotNull(Color.BLUE)
    })

    it('Test: Color.GREEN', () => {
      chai.assert.isNotNull(Color.GREEN)
    })

    it('Test: Color.YELLOW', () => {
      chai.assert.isUndefined(Color.YELLOW)
    })
  })

  describe('Test Color [From Object]', function() {
    let Color = new Enum({
      RED:   { hex: '#F00' },
      BLUE:  { hex: '#0F0' },
      GREEN: { hex: '#00F' }
    })
    
    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      let red = Color.RED
      chai.assert.isNotNull(red)
      red.getHex().should.equal('#F00')
      JSON.stringify(red).should.equal('{"hex":"#F00"}')
    })

    it('Test: Color.BLUE', () => {
      let blue = Color.BLUE
      chai.assert.isNotNull(blue)
      blue.getHex().should.equal('#0F0')
      JSON.stringify(blue).should.equal('{"hex":"#0F0"}')
    })

    it('Test: Color.GREEN', () => {
      let green = Color.GREEN
      chai.assert.isNotNull(green)
      green.getHex().should.equal('#00F')
      JSON.stringify(green).should.equal('{"hex":"#00F"}')
    })

    it('Test: Color.YELLOW', () => {
      let yellow = Color.YELLOW
      chai.assert.isUndefined(yellow)
    })
  })

  mocha.run()
}

class Enum {
  constructor(...values) {
    this.__values = []

    const [first, ...rest] = values
    const hasOne = rest.length === 0
    const isArray = Array.isArray(first)
    const args = hasOne ? (isArray ? first : Object.keys(first)) : values

    args.forEach((name, index) => {
      this.__createValue({
        name,
        index,
        props: hasOne && !isArray ? first[name] : null
      })
    })

    Object.freeze(this)
  }

  /* @public */
  values() {
    return this.__values
  }

  /* @private */
  __createValue({ name, index, props }) {
    const value = Object.create(Enum.__prototype(props))

    Object.defineProperties(value, Enum.__defineReservedProps({
      name,
      index
    }))

    if (props) {
      Object.defineProperties(value, Enum.__defineAccessors(props))
    }

    Object.defineProperty(this, name, {
      value: Object.freeze(value),
      writable: false
    })

    this.__values.push(this[name])
  }
}

Enum.__prototype = (props) => ({
  toJSON() {
    return props;
  },
  toString() {
    return JSON.stringify(props);
  }
});

/* @private */
Enum.__defineReservedProps = ({ name, index }) => ({
  name: {
    value: Symbol(name),
    writable: false
  },
  ordinal: {
    value: index,
    writable: false
  }
})

/* @private */
Enum.__defineAccessors = (props) =>
  Object.entries(props).reduce((acc, [prop, val]) => ({
    ...acc,
    [prop]: {
      value: val,
      writable: false
    },
    [`get${Enum.__capitalize(prop)}`]: {
      get: () => function() {
        return this[prop]
      }
    }
  }), {})

/* @private */
Enum.__capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1)

main()
.as-console-wrapper { top: 0; max-height: 100% !important; }
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.2.0/chai.js"></script>
<!--

public enum Color {
  RED("#F00"),
  BLUE("#0F0"),
  GREEN("#00F");
  
  private String hex;
  public String getHex()  { return this.hex;  }
  
  private Color(String hex) {
    this.hex = hex;
  }
}

-->
<div id="mocha"></div>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
2

Maybe this solution ? :)

function createEnum (array) {
  return Object.freeze(array
    .reduce((obj, item) => {
      if (typeof item === 'string') {
        obj[item.toUpperCase()] = Symbol(item)
      }
      return obj
    }, {}))
}

Example:

createEnum(['red', 'green', 'blue']);

> {RED: Symbol(red), GREEN: Symbol(green), BLUE: Symbol(blue)}
MarkMYoung
  • 141
  • 1
  • 9
2

Here is an Enum factory that avoids realm issues by using a namespace and Symbol.for:

const Enum = (n, ...v) => Object.freeze(v.reduce((o, v) => (o[v] = Symbol.for(`${n}.${v}`), o), {}));

const COLOR = Enum("ACME.Color", "Blue", "Red");
console.log(COLOR.Red.toString());
console.log(COLOR.Red === Symbol.for("ACME.Color.Red"));
gogo
  • 950
  • 12
  • 20
1

you can also use es6-enum package (https://www.npmjs.com/package/es6-enum). It's very easy to use. See the example below:

import Enum from "es6-enum";
const Colors = Enum("red", "blue", "green");
Colors.red; // Symbol(red)
David Callanan
  • 5,601
  • 7
  • 63
  • 105
Fawaz
  • 21
  • 2
0

I prefer @tonethar's approach, with a little bit of enhancements and digging for the benefit of understanding better the underlyings of the ES6/Node.js ecosystem. With a background in the server side of the fence, I prefer the approach of functional style around platform's primitives, this minimizes the code bloat, the slippery slope into the state's management valley of the shadow of death due to the introduction of new types and increases the readability - makes more clear the intent of the solution and the algorithm.

Solution with TDD, ES6, Node.js, Lodash, Jest, Babel, ESLint

// ./utils.js
import _ from 'lodash';

const enumOf = (...args) =>
  Object.freeze( Array.from( Object.assign(args) )
    .filter( (item) => _.isString(item))
    .map((item) => Object.freeze(Symbol.for(item))));

const sum = (a, b) => a + b;

export {enumOf, sum};
// ./utils.js

// ./kittens.js
import {enumOf} from "./utils";

const kittens = (()=> {
  const Kittens = enumOf(null, undefined, 'max', 'joe', 13, -13, 'tabby', new 
    Date(), 'tom');
  return () => Kittens;
})();

export default kittens();
// ./kittens.js 

// ./utils.test.js
import _ from 'lodash';
import kittens from './kittens';

test('enum works as expected', () => {
  kittens.forEach((kitten) => {
    // in a typed world, do your type checks...
    expect(_.isSymbol(kitten));

    // no extraction of the wrapped string here ...
    // toString is bound to the receiver's type
    expect(kitten.toString().startsWith('Symbol(')).not.toBe(false);
    expect(String(kitten).startsWith('Symbol(')).not.toBe(false);
    expect(_.isFunction(Object.valueOf(kitten))).not.toBe(false);

    const petGift = 0 === Math.random() % 2 ? kitten.description : 
      Symbol.keyFor(kitten);
    expect(petGift.startsWith('Symbol(')).not.toBe(true);
    console.log(`Unwrapped Christmas kitten pet gift '${petGift}', yeee :) 
    !!!`);
    expect(()=> {kitten.description = 'fff';}).toThrow();
  });
});
// ./utils.test.js
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • `Array.from(Object.assign(args))` does absolutely nothing. You could just use `...args` directly. – Domino Apr 06 '20 at 06:56
0

Here is my approach, including some helper methods

export default class Enum {

    constructor(name){
        this.name = name;
    }

    static get values(){
        return Object.values(this);
    }

    static forName(name){
        for(var enumValue of this.values){
            if(enumValue.name === name){
                return enumValue;
            }
        }
        throw new Error('Unknown value "' + name + '"');
    }

    toString(){
        return this.name;
    }
}

-

import Enum from './enum.js';

export default class ColumnType extends Enum {  

    constructor(name, clazz){
        super(name);        
        this.associatedClass = clazz;
    }
}

ColumnType.Integer = new ColumnType('Integer', Number);
ColumnType.Double = new ColumnType('Double', Number);
ColumnType.String = new ColumnType('String', String);
Stefan
  • 10,010
  • 7
  • 61
  • 117
0
const Colors = (function(Colors) {
  Colors[Colors["RED"] = "#f00"] = "RED";
  return Object.freeze(Colors);
})({});
Colors.RED = "#000" // <= Will fail because object is frozen
console.log(Colors.RED); // #f00
console.log(Colors['#f00']); // RED
Greg
  • 1,007
  • 1
  • 9
  • 9
0

I use strings augmented with JSDoc which is compatible with VSCode / VSCodium. It's efficient, simple and safe e.g.:

/** @typedef { 'red' | 'green' | 'blue' } color */

/** @type {color} */
let color = 'red'

/**
 * @param {color} c
 */
function f(c) {}
Jonathan
  • 1,007
  • 16
  • 12
0

Another approach to the list using ES2022

class Enum {
  static toEnum() {
    const enumMap = new Map();
    for (const [key, value] of Object.entries(this)) {
      enumMap.set(key, value);
    }
    this.enumMap = enumMap;
  }

  static [Symbol.iterator]() {
    return this.enumMap[Symbol.iterator]();
  }

  static getValueOf(str) {
    return this.enumMap.get(str);
  }
}


class ActionTypes extends Enum {
  static REBALANCE = Symbol("REBALANCE");
  static MESSAGE = Symbol("MESSAGE");
  static FETCH = Symbol("FETCH");
  static { this.toEnum() }
}
Robin F.
  • 1,137
  • 11
  • 19
-3

You could use ES6 Map

const colors = new Map([
  ['RED', 'red'],
  ['BLUE', 'blue'],
  ['GREEN', 'green']
]);

console.log(colors.get('RED'));
Valentin Micu
  • 73
  • 1
  • 6
  • IMHO it's a bad solution because of its complexity (should call the accessor method every time) and сontradiction of the enum nature (can call the mutator method and change a value of any key)... so use `const x = Object.freeze({key: 'value'})` instead to get something that looks and behaves like enum in ES6 – Yurii Rabeshko Sep 22 '18 at 22:38
  • You must pass a string in order to get the value, as you did colors.get('RED'). Which is error prone. – adrian oviedo Sep 09 '19 at 17:09