Using Enums in pure JavaScript

Oleh Baranovskyi
4 min readMar 8, 2018

Enum(or Enumerated type) is a special type used to define collections of constants.

To give an illustration of what means Enum, we should have a look on Java example first:

public enum Level {
HIGH,
MEDIUM,
LOW
}
...
Level level = Level.HIGH;

I guess nothing special and there is no need to fall in details.
Here we are going to mimic something similar using pure JavaScript.

Main target of current article is to give you an idea that can be improved as much as possible.

What we need:

  1. We need a class without creating new instance of it
  2. We need a static variables that cannot be changed
  3. We need a enum utility helpers in order to facilitate work with it

Step 1:

Defining constructor:

function Days {}

In order to avoid creation of new instances we should return in all cases Days object, yup nothing more (you can read more about JavaScript constructors in my article here)

let log = console.log;function Days() {
return Days;
}
var d1 = new Days()
, d2 = new Days
, d3 = Days();
log(Days === d1); //true
log(d1 === d2); // true
log(d3 === d2); // true
log(d1 instanceof Days); // false
log(d2 instanceof Days); // false

Now we have a base for our enum, so we can move to next steps.

Step 2:

Creating a static immutable variables in our enum.

// adding static variable
Days.MONDAY = 0;
log(Days.MONDAY); // 0
// Enums by nature can't be changed
// and here we are changing it
Days.MONDAY = 10;
log(Days.MONDAY); // 10
// so we should use Object.defineProperty()
Object.defineProperty(Days, 'MONDAY', {
configurable: false,
writable: false,
enumerable: true,
value: 0
});
// and now it cannot be changed
Days.MONDAY = 10;
log(Days.MONDAY); // 0

But why configurable property set to false isn’t it enough to set only writable property to false?

- Answer is no because somebody can once again define same property using Object.defineProperty() or Object.defineProperties() and set writable to true (or just change value in property descriptor), like in the following example:

Object.defineProperty(Days, 'MONDAY', {
configurable: true,
writable: false,
enumerable: true,
value: 0
});
Days.MONDAY = 1;
log(Days.MONDAY); // 0
Object.defineProperty(Days, 'MONDAY', {
configurable: true,
writable: true,
enumerable: true,
value: 0
});
Days.MONDAY = 1;
log(Days.MONDAY); // 1

Step 3:

Java enums have a methods .valueOf() and .values(). I don’t see the reason to implement .valueOf() method and if you take a look on java enums you will understand why. We are going to implement a .values() method.

Days.values = function() {
return Object.keys(Days)
.filter(key => typeof Days[key] !== 'function');
}
log(Days); // ['MONDAY']

but what will happen if we add one more day for example Sunday before Monday?

Object.defineProperty(Days, 'SUNDAY', {
configurable: false,
writable: false,
enumerable: true,
value: 6
});
Object.defineProperty(Days, 'MONDAY', {
...
log(Days.values()); // ['SUNDAY', 'MONDAY']

We are getting incorrect order because Sunday have number 6 and he located before Monday. Let’s fix our .values() method and even add new .keys() and .getByValue() method.

Days.values = function() {
return Object.keys(Days)
.filter(key => typeof Days[key] !== 'function')
.sort((key1, key2) => Days[key1] - Days[key2]);
}
Days.keys = function() {
return Days.values()
.map(key => Days[key]);
}
Days.getByValue = function(val) {
return Days.values()
.find(dayValue => Days[dayValue] === val);
}
// after fix
log(Days.values()); // ['MONDAY', 'SUNDAY']
log(Days.keys()); // [0, 6]
let day = Days.SUNDAY;
log(Days.getByValue(day)); // 'SUNDAY'
// now let's test it
let day1 = Days.MONDAY;
switch(day1) {
case Days.MONDAY:
log('Monday');
break;
case Days.SUNDAY:
log('Sunday');
break;
default:
log('nothing');
// output: Monday

Now you probably will say: Great seems that all is working as we expected!
But I wouldn’t say like this. Imagine that we want to use more advanced enum architecture and now we want to store an object value in enum .

Object.defineProperty(Days, 'MONDAY', {
configurable: false,
writable: false,
enumerable: true,
value: {
name: 'Monday',
dayNumber: 1,
holiday: {
name: 'Women\'s Day'
}
}
});
// and let's do like this
Days.MONDAY.name = 'something other';
log(Days.MONDAY.name); // something other

What? But It‘s expected and I will explain why. Lower level (inner object in current case it’s holiday) is not frozen!

let nameDescriptor = Object.getOwnPropertyDescriptor(Days.MONDAY, 'name'); 
log(nameDescriptor.configurable); // true
log(nameDescriptor.writable); // true

In order to fix that issue we should freeze inner objects using Object.freeze() method and if to be honest I would suggest to use deepFreeze() function because it will freeze all inner objects. You can find deepFreeze() function in my previous article here (you only need to copy that function and paste it in current example)

// updated version
Object.defineProperty(Days, 'MONDAY', {
configurable: false,
writable: false,
enumerable: true,
value: deepFreeze({
name: 'Monday',
dayNumber: 1,
holiday: {
name: 'Women\'s Day'

}
})
});
// and let's do like this
Days.MONDAY.name = 'something other';
log(Days.MONDAY.name); // Monday

and now let’s check property descriptors:

let nameDescriptor = Object.getOwnPropertyDescriptor(Days.MONDAY, 'name'); 
log(nameDescriptor.configurable); // false
log(nameDescriptor.writable); // false

Seems our target was successfully accomplished.

Here is a full example:

var log = console.log;function defineEnumProperty(ctx) {
return (prop, value) => {
Object.defineProperty(ctx, prop, {
configurable: false,
writable: false,
enumerable: true,
value: value
});
}
}
function Days() {
return Days;
}
Days.values = function() {
return Object.keys(Days)
.filter(key => typeof Days[key] !== 'function')
.sort((key1, key2) => Days[key1] - Days[key2]);
}
Days.keys = function() {
return Days.values()
.map(key => Days[key]);
}
Days.getByValue = function(val) {
return Days.values()
.find(dayValue => Days[dayValue] === val);
}
var addEnumValue = defineEnumProperty(Days)
, days = ['MONDAY', 'TUESDAY', 'WEDNESDAY',
'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'];
days.forEach((day, index) => {
addEnumValue(day, index);
});
log(Days.values()); // ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]
log(Days.keys()); // [0, 1, 2, 3, 4, 5, 6]
let day = Days.SUNDAY;
log(Days.getByValue(day)); // 'SUNDAY'
log(Days.FRIDAY); // 4

Conclusion

Thank you guys for reading. I hope you enjoyed it and learned some new stuff related to JavaScript. Please subscribe and press ‘Clap’ button if you like this article.

--

--