This post will explain how to implement and use enumerations (or enum types) in Javascript.
Enums are types that contain a limited number of fixed values, as opposed to types like Number
or String
which can have a wide range of values.
This is useful for many situations: For example, when describing seasons in temperate climates, you’ll want to limit the options to certain possible values: Summer
, Winter
, Spring
and Autumn
.
Javascript Language Support for Enums
Unlike other languages like C# or Java, Javascript Does Not Have Native Support for enums.
There are several ways to implement enums in Javascript, and we’ll look at each of them in this post. However, in the absence of native language support, each implementation will have its own tradeoffs.
Javascript does have a reserved keyword called
enum
, but at present it is not used for anything in the language. It is reserved for future use.
Defining Enums as Object Keys
For a basic implementation of enums, we can define an object to encapsulate the enum type, and assign a key for each enum value.
For example, we can represent seasons as on object with each season as the key, and a representative string as the value:
const Seasons = {
Summer: "summer",
Autumn: "autumn",
Winter: "winter",
Spring: "spring"
}
We can also do this by using numbers as values:
const Seasons = {
Summer: 0,
Autumn: 1,
Winter: 2,
Spring: 3
}
While this would work for small code bases, there are some limitations when using this approach:
It’s easy to make mistakes in your code. A developer can make the mistake of using integers outside the range of the ones defined, or make a spelling mistake when using strings.
const Seasons = { Summer: "summer", Autumn: "autumn", Winter: "winter", Spring: "spring" } const mySeason = "summr" // this would be false because of the spelling mistake in `mySeason` console.log(mySeason === Seasons.Summer)
``
Definitions from unrelated enums can overlap and cause conflicts:
const Seasons = { Summer: 0, Autumn: 1, Winter: 2, Spring: 3 } const Fruits = { Apple: 1, Orange: 1 } // Ideally, this should never be true! console.log(Seasons.Summer === Fruits.Apple)
Using primitives (like strings or numbers) as values is semantically incorrect - Seasons are not really integers or strings – they’re seasons and should have their own types for better semantic representation.
Enums with Symbols
Symbols let us define values that are guaranteed not to collide with one another.
For example:
const Summer1 = Symbol("summer")
const Summer2 = Symbol("summer")
// Even though they have the same apparent value
// Summer1 and Summer2 don't equate
console.log(Summer1 === Summer2)
// false
console.log(Summer1)
We can define our enums using Symbols to ensure that they are not duplicated:
const Seasons = {
Summer: Symbol("summer"),
Autumn: Symbol("autumn"),
Winter: Symbol("winter"),
Spring: Symbol("spring")
}
let season = Seasons.Spring
switch (season) {
case Seasons.Summer:
console.log('the season is summer')
break;
case Seasons.Winter:
console.log('the season is winter')
break;
case Seasons.Spring:
console.log('the season is spring')
break;
case Seasons.Autumn:
console.log('the season is autumn')
break;
default:
console.log('season not defined')
}
By using symbols, we can be sure that an enum variable will always have one of the values that we defined.
Please note that Symbols cannot be serialized into JSON, so if you plan on converting an object containing this enum into a JSON string, you should consider the previous method of using object keys instead
Making Enum Objects Immutable
In the previous examples, it’s possible to change the value of an enum by modifying the enum object. For example, we can change the Seasons
object like so:
const Seasons = {
Summer: Symbol("summer"),
Autumn: Symbol("autumn"),
Winter: Symbol("winter"),
Spring: Symbol("spring")
}
Seasons.Winter = "winter" // this will overwrite the `Symbol` with a string
For larger code bases, these kind of errors may be introduced unintentionally. We can use the native Object.freeze
method to prevent these kind of changes:
const Seasons = Object.freeze({
Summer: Symbol("summer"),
Autumn: Symbol("autumn"),
Winter: Symbol("winter"),
Spring: Symbol("spring")
})
Seasons.Winter = "something else" // this won't change the `Seasons` object because its been frozen
console.log(Seasons.Winter)
// Output:
// Symbol(winter)
In general, it is always a good idea to use Object.freeze
on enum types because it prevents accidental changes to the enum values.
Defining Enums with Classes
To make our code more semantically correct, we can create a class to hold groups of enums.
For example, our seasons should have a way for us to identify that they all belong to a similar classification.
Let’s see how we can use classes and objects to create distinct enum groups:
// Season enums can be grouped as static members of a class
class Season {
// Create new instances of the same class as static attributes
static Summer = new Season("summer")
static Autumn = new Season("autumn")
static Winter = new Season("winter")
static Spring = new Season("spring")
constructor(name) {
this.name = name
}
}
// Now we can access enums using namespaced assignments
// this makes it semantically clear that "Summer" is a "Season"
let season = Season.Summer
// We can verify whether a particular variable is a Season enum
console.log(season instanceof Season)
// true
console.log(Symbol('something') instanceof Season)
//false
// We can explicitly check the type based on each enums class
console.log(season.constructor.name)
// 'Season'
The advantage of using classes is that each enum group is now a distinct type, and we can use the instanceof
operator to check whether a variable is an enum of a particular type.
Additionally, this information is contained within the enum itself, so we don’t need to keep track of the type separately.
Listing All Possible Enum Values
If we used the class-based approach above, we can loop through the keys of the Season
class to obtain all the enum values under the same group:
Object.keys(Season).forEach(season => console.log("season:", season))
// season: Summer
// season: Autumn
// season: Winter
// season: Spring
We can do the same with the object-based approach as well:
const Seasons = Object.freeze({
Summer: Symbol("summer"),
Autumn: Symbol("autumn"),
Winter: Symbol("winter"),
Spring: Symbol("spring")
})
Object.keys(Seasons).forEach(season => console.log("season:", season))
// season: Summer
// season: Autumn
// season: Winter
// season: Spring
When to Use Enums in Javascript?
Enums are helpful if there are a definite number of fixed values for any one variable.
For example, the crypto standard library for Node.js has a list of supported algorithms, that can be considered an enum group.
In general, you will be better off using enums when:
- When you need to represent a fixed set of values with meaningful names. For example, you could use an enum to represent the days of the week, the months of the year, or the seasons.
- When you need to prevent errors by preventing you from accidentally assigning an invalid value to a variable. For example, you could use an enum to represent the possible values of a button’s state, like “active” or “disabled”.
However, if you need to represent a set of values that can change over time, or if you need to represent a set of values that are not fixed, then you should not use enums.
Using enums in Javascript correctly will lead to better code that is more stable, easier to read and less error prone.