Creating Enum types in Javascript

Nitzan Hen
The Startup
9 min readMay 24, 2020

--

In creating advanced Enum types in Typescript, we used Typescript’s handy readonly and private access modifiers to create an enum pattern with considerable flexibility and advanced features. The next logical step would be to form a parallel pattern for enums in Javascript — for cases where a Javascript environment is preferred or required.

The catch here is that Javascript, and more specifically Javascript ES5 and ES6 — the most popular target versions — have no support for readonly properties, and most definitely not for private accessors. To implement an enum pattern here, we’ll need to use deeper, perhaps not as common tools or patterns. And that’s not too bad! a lot can be learned from just playing around with those, as we will be doing soon. To be more accurate, we’ll be using some less frequent methods of the Object class, and in ES6 the Proxy API, too.

Why use enums in Javascript?

If you’re not sure what enums are, or why would one use them (especially in a language that does not natively support them, such as Javascript) — we’ve discussed enums in general, as well as when they may be useful, in the previous article.

To put it briefly, enums are a special type of “closed” class, all the instances of which are well defined at compile time, and accessible through the class itself (typically as static variables, e.g. Enum.INSTANCE). They’re useful for modelling a type whose instances are predefined and (usually) constant — allowing better type enforcement and readability.

For example, CSS properties such as ‘position’ or ‘display’, constant sets of object such as the planets in the solar system, and many linguistic concepts (tenses, for example) can be properly modelled using enum types, and the list goes on; truly, many apps have constant objects or entities which can be upgraded through using enums.

Also, specifically in Javascript — where there’s no native implementation of enum types, and to recreate them we need to use advanced patterns & features of the language — learning how to recreate advanced enums as a pattern has high educational value, as it exposes some abilities that developers might not come across often.

Let us start with the simplest, most basic implementation:

Simplest, plain implementation

Suppose your need is a simple, constant key-value mapping (for lookup or indexing, for example); as an example, let’s consider a map between HTTP response error codes and indicative messages the app displays when those are returned:

Obviously, this is a simplified example, but one might get the corresponding message to some HTTP request’s returned status code, and display it to the user. However, you would typically want the status-message pairs to stay constant — you wouldn’t want someone (another programmer working on the project or yourself) to change 200’s message to “Big bad error!”, for example. And you would usually not want someone to add new messages or remove existing ones, like for status -1, as the status codes are constant.

There’s a Javascript method that does exactly that: Object.freeze(). According to MDN:

The Object.freeze() method freezes an object. A frozen object can no longer be changed; freezing an object prevents new properties from being added to it, existing properties from being removed […] and prevents the values of existing properties from being changed. In addition, freezing an object also prevents its prototype from being changed. freeze() returns the same object that was passed in.

Freezing our status codes is very simple: we just have to pass it to the function:

Object.freeze(status_codes);

The function freezes the argument and returns it; ideally, we’d declare this object on a dedicated file, and export it:

export default Object.freeze(status_codes);

And that’s it! you have a simple enum — a constant, static mapping of status codes to messages. Enums — and more generally, frozen objects — can be used in many places to improve your code and prevent easily overlooked errors, especially when working in teams. It’s good practice to freeze objects that are intended to be static. Note, however, that Object.freeze() does not freeze deeply — if you have nested objects, consider calling Object.freeze() on them too.

These sorts of enums are parallel to Typescript’s built-in enum implementation. As we’ve seen on the previous discussion, though, if we think of enums as a special type of classes we can get pretty advanced with them; luckily, we can do that in Javascript, too, using a few tricks.

We’ll look at two advanced enum implementations in Javascript: the first works on ES5 (and later versions, of course), and makes use of Javascript’s prototype system. The second uses the Proxy API, which was introduced in ES6.

Implementing in ES5

First of all, as support for ES6 is widespread today, either directly or through polyfills/babel, the ES5 implementation’s value is mostly educational, in my opinion.

With Object.freeze() (or a similar method, as we’ll see soon) we can prevent properties of an object — or more specifically, a class instance — from being modified, added, or removed. Therefore, the main problem we face is hiding the class’s constructor (making it private, in terms of OOP), so that new instances cannot be created dynamically. This problem turns out to be more complex than it might seem, though, as we actually do need the constructor, to construct our instances, and only then, somehow, make it disappear.

The solution to this takes advantage of JS classes being just functions of a certain type (constructors): we create a class (a function), e.g. Enum, with a “never” constructor — attempting to construct an Enum instance throws an error. Then, we create another class — EnumInner — which inherits Enum, but has a proper constructor; we then use the constructor to create instance of EnumInner (which are, in turn, also instances of Enum), as well as desired static properties and methods, all of which we bind to the Enum class. Lastly, we expose the Enum class, and not the EnumInner class, thereby preventing the construction of new instances from outside.

It is very convenient to do all of the above inside a function, specifically an anonymous function, which we call right after defining it. That way, utilizing the way scoped variables and functions work in Javascript, we can expose only the public constructor (Enum). This is actually a common Javascript pattern; Typescript, for example, uses it when transpiling namespaces to Javascript. Again, it is also recommended to declare the enum in a separate file, and export it.

Let us implement the Todo Status type from the previous article, using this pattern:

First, we define the main layout of the class:

Note that we’re defining an anonymous function and calling it immediately — this allows us to define the EnumInner class without it being accessible from outside.

Next, let’s define the public and inner constructors, and make the inner class extend the outer class by setting its prototype:

The toJSON() method provides easy serialization of Status objects.

Let us now consider adding the instances themselves, and static methods, including a .values getter to get all of the enum’s instances. Adding them should be pretty straightforward — we can assign them to Status, which is later exposed, and they’ll be exposed along with it. However, we need to prevent changing them from the outside, something parallel to Typescript’s readonly modifier. And we can’t just freeze them — the instance references could still be deleted or completely reassigned from outside the class (calling Status.COMPLETED = null, for example).

Fortunately, the Object class has our backs here too: for these kinds of purposes, the Object.defineProperty() method exists. According to MDN:

This method allows a precise addition to or modification of a property on an object. […] By default, values added using Object.defineProperty() are immutable and not enumerable.

In essence, Javascript objects (in general) have extra flags behind the scenes that determine their behaviour, most specifically when attempting to mutate, delete, or enumerate them. The Object.defineProperty() allows us to configure those flags. We’ll be using a sibling of this function, Object.defineProperties(), which allows to define more than one such property at a time.

If you’re not familiar with those functions, do check out Object.defineProperty() and Object.defineProperties(). They’re essential to understanding Javascript behind the scenes, and can teach you a lot.

Back to our status class, using the Object.defineProperties() method is pretty simple. we can define construct all the instances we want, using the StatusInner constructor:

Note that instances are marked as enumerable: that means they can be iterated over by Object.keys() or a for…of loop, for example. The enumerable flag is false by default in Object.defineProperties(), so we need to pass it explicitly.

Now, Also, let us define some static methods and a .values getter; these should not be enumerable:

Note the special syntax on the values property: it has a get field, which defines a getter (a getter allows us to access values like Status.values instead of Status.values(), basically). The fromString() function returns a Status by its name, or throws an Error if there’s no Status with the corresponding name (just like in the previous article).

Lastly, we need to freeze the public Status class, and return (expose) it:

And we’re done! Let’s look at the complete example:

This complete example can, of course, be easily generalized into a generic Enum class, and even into a snippet. A generic Enum class, following this implementation, can be found here.

Implementing in ES6 using the Proxy API

Well, the ES5 solution works, but it’s definitely verbose (a simple implementation, as seen in the complete example above, takes 100 lines of code!), and quite complicated. In ES6, among many other useful tools, we received the Proxy API, which will allow us to present an alternate, concise and more intuitive solution:

As its name suggest, the Proxy API (accessible using the Proxy class) controls all access to an object. Many progressive frameworks, such as Vue.js, use the Proxy API extensively. We can utilize it by wrapping a certain object, function or class in a Proxy constructor:

new Proxy(*target, handler*)

The target, of course, is the entity you want to create the proxy for; the handler is an object containing “traps” — handlers for different operations you can perform on an object, such as getting & setting fields, deleting fields, and most importantly for us — constructing new instances. The MDN page on Proxy describes all the different handler traps you can use; I encourage you to check it out.

The traps we’re interested in here are construct(), defineProperty(), deleteProperty(), set() and setPrototypeOf(); when trying to perform each of these operations on the enum class, we want to throw a TypeError with a related message. Note that these error are not compile-time errors (unfortunately, Javascript has no way to validate that these operations are not called at compile time), but they still prevent modifying the class from outside.

Also, note that using Proxy wraps our class, and access to the original Enum class can still be exposed. Thus, it is all the more encouraged to declare the Enum class in a separate file, and export only the Proxy, preferably as the default export.

Let’s get down to business — this time, we’ll do it generically from the start. First, let’s define a standard Enum class:

Most importantly, note line 19 (Object.freeze(this)). This prevents “deep” changes to the Enum class, I.E changes to properties of its instances, by freezing the instance after construction.

Next, let’s define the Proxy, and finally export it:

“Enum.name” is the class name (in this case, “Enum”).

And the complete example:

We managed to reduce our code by half its length! And now, it is much more readable and straightforward. It works like a charm, too:

Conclusion

In this article, we discussed enums and the enum pattern in Javascript. We reviewed why and when they may be important, and three ways to implement them — a simple method, a method in ES5 and an ES6 method using Proxies. The enums’ value by themselves aside, we learned some Object methods that can be really useful (freeze(), defineProperty(), and getPrototypeOf()/setPrototypeOf()), as well as the basics of the Proxy API (and a useful pattern for ES5 sophisticated classes), and a lot of what’s going behind the scenes in Javascript.

If you haven’t, do check out the previous article, creating advanced Enum types in Typescript, where we went over a similar process in Typescript; the implementation there was more intuitive and straightforward, as Typescript already has basic OOP capabilities.

I hope you enjoyed this article, and have learnt something from it. I would love to hear your thoughts and feedback.

--

--

Nitzan Hen
The Startup

Full stack developer & undergrad student of Mathematics; very passionate about both. Enthusiastic about learning, teaching, writing, open source & more.