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
.
Let’s look at different ways of implementing this type of data:
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 codebases, we will face a few immediate issues:
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')
}
Using Symbols ensures that the only way we can assign an enum value is by using the values that we defined initially.
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 codebases, these kind of errors may be introduced unintentionally. We can use Object.freeze
to prevent these kind of changes:
const Seasons = Object.freeze({
Summer: Symbol("summer"),
Autumn: Symbol("autumn"),
Winter: Symbol("winter"),
Spring: Symbol("spring")
})
Seasons.Winter = "winter" // this won't change the `Seasons` object because its been frozen
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'
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?
In general, 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.
Using enums in Javascript correctly will lead to better code that is more stable, easier to read and less error prone.