Enums in JavaScript

Michael P Smith
5 min readJul 4, 2020

--

Photo by Markus Spiske from Pexels

A recent post I saw asked how to use Enums in JavaScript, and some of the answers pointed them towards TypeScript. And that’s a good answer … but … it doesn’t solve the problem. How do you use Enums in JavaScript? Let’s explore that.

Before you read further:

There is no such thing as native Enums in JavaScript. At least, not in the traditional Java/C#/WhatEver Language sense of the word. If you’re looking for a built-in Enum, you’re out of luck. You will need to build your own.

Luckily, it’s very easy.

The Enum Requirements

This is what I want to see happen with this class:

let animals = new Enum("Cat", "Dog", "Horse")
console.log(animals.Cat);
console.log(animals.Dog);
console.log(animals.Horse);
animals.Cat = "foobar"; // no change because all are constants
animals.Camel = "foobar" // not added. Only originally declared
// propeties accessible

We are going to start with creating a basic Enum class. Our requirements are:

  1. We declare any number of Enum constants as arguments to the class constructor.
  2. The class will automatically assign a value to those constants.
  3. No constant value can be changed
  4. No items can be added or removed from the class

The Enum Class

Here is the class definition I came up with:

class Enum {
constructor(...properties) {
let value = 0;
properties.forEach( function (prop) {
const newValue = value;
Object.defineProperty(this, prop, {
get: function() { return newValue; }
});
value++;
}, this);
Object.freeze(this);
}
}

Let’s take a look as to what’s going on:

First, notice that it has a constructor, but nothing else. This is because there is no need for any other properties. Why? Each property is dynamically created based upon the arguments to the constructor. There is no need to define other properties.

Notice also the “rest” syntax as the argument to the constructor. This will take all the arguments we pass to the class and move them into an array. Which means that we can pass a zillion arguments to the constructor and it doesn’t matter to us, as we can access all the arguments as elements of the array. This satisfies Requirement #1 above.

The Dynamically Created Property

Here’s where the magic occurs. Let’s look at the code:

properties.forEach( function (prop) {
const newValue = value;
Object.defineProperty(this, prop, {
get: function() { return newValue; }
});
value++;
}, this);

We loop through the array of all the arguments we pass to the constructor. For each of those properties, we then use “Object.defineProperty” to dynamically add the property to the class. There are a number of things your can do with “Object.defineProperty”, but for us, the important thing is defining “getters” and “setters”. That is, defining a function on how one “gets” the value of the property, and when that property is changed, defining how that value is “set”.

Notice that we’ve only defined “get” and not “set”. This is on purpose. By doing that, any application accessing the property will always get a value, but when they try to set it (change it), nothing will occur. This makes the property “read only”, and satisfies one of our requirements — Requirement #3.

Finally, the “get” function assigns a number to the property, and increments that number before the next entry in the properties array is called. This effectively assigns an incrementally changing number to each of the properties in order. This satisfies Requirement #2. Why did we reassign “value” to “newValue”? Isn’t that meaningless? Tell you what — pop the code into Node (or a browser) change “newValue” to “value” in the “get” function, and tell me what happens. Extra credit: Tell my why it happens.

Freezing the Object

Finally, we have Requirement #4, where nothing can change. This is an easy one-liner:

Object.freeze(this);

Once all the looping is done, we “freeze” the class so that no more changes can be made. This prevents an app from doing something like this:

let animals = new Enum("Cat", "Dog", "Horse");
animals.Coyote = "my New Animal";

Normally, performing “animals.Coyote” will add the property “Coyote” to the object, and therefor violating Requirement #4. “Freezing” the object prevents this.

Just remember that JavaScript will not throw any exception if you try to add a new property to the object.

Assigning Values to Enums

This is all well and good, but what if you want to assign values to your Enum constants instead of the class doing it for you? Basically eliminating Requirement #2. Some languages let you do this, and you can make a small change in your code to permit that.

Let’s say your assignment now looks like this:

let animals = new Enum(
{ name: "Cat", value: 2 },
{ name: "Dog", value: 4 },
{ name: "Horse", value: 114 }
);

Instead of just passing the names of the properties, you are passing the names and associated values.

Your class looks like this:

class Enum {
constructor(...properties) {
properties.forEach( function (prop) {
const newValue = prop.value;
Object.defineProperty(this, prop.name, {
get: function() { return newValue; }
});
}, this);
Object.freeze(this);
}
}

It’s actually a little smaller than the previous example because it’s not having to increment a counter. The big difference is that the “forEach” loop is now passing an Object to us with two properties, “name” and “value”. The only thing that changed was the assignment of the “newValue” variable from “value” to “prop.value” and the second argument to the “Object.defineProperty” method, which changed from “prop” to “prop.name”.

This new definition will now let you not only create your enums, but also assign values to them.

Summary

If you miss your Enum classes and data constructs in other languages once you went to JavaScript, the above should get you started in recreating Enums and making your life happy again.

See you later!

P.S.

Extra Credit: Do this:

let animals = new Enum("Cat", "Dog", "Horse");
console.log(animals);
// displays the following:
// Enum {}

You’ll see nothing listed. It’s all because of the arguments passed to “Object.defineProperty”. Look it up and figure out what you need to do to get the following listing:

let animals = new Enum("Cat", "Dog", "Horse");
console.log(animals);
// displays the following:
// Enum { Cat: [Getter], Dog: [Getter], Horse: [Getter] }

--

--

Michael P Smith

Known to no one else as “TheVirtuoid”. Developer since the days of the dinosaurs. Sees programming as an art form. Loves JavaScript and helping others learn.