There’s a ton of information on the internet related to the best way to create JavaScript enums. This post is different in that it focuses on using enums that are immutable and cannot be changed within your own custom libraries.
The challenge with enum usage in JavaScript is global variable leakage can accidentally cause unexpected changes within your application, especially in cases of large, complex applications that may involve your own JavaScript libraries along-side 3rd party libraries. Global variable leakage means two variables can potentially have the same name, and this may not cause an error. Good debuggers help to avoid this problem by using jslint. But, if someone else is using your libraries then you can’t depend on them using best practices. So, variable leakage can cause unexplainable/unpredictable problems and create hard to track bugs. What we need is a coding pattern to protect enums so that we can guarantee that we always get the value expected.
As of today, JavaScript doesn’t have a universally accepted, built-in cross-browser solution for guaranteeing that specific variables can be made immutable. In other words, if we create a statement var BLUE = “blue” there is no way to enforce that something elsewhere in an application, or code that someone else wrote that is running the same application cannot ever change the value of BLUE.
In comparison, strongly typed languages such as C# and Java let you declare constants. If you accidentally try to change them in your code you get a compiler error that prevents the application from running. The compile-time checking can help prevent bugs in your code later on. Here are several examples from Java and C#:
final int RADIUS = 1000; //Java
const int RADIUS = 1000; //C#
So here we go. I’ll use six use cases to illustrate a variety of ways to make certain your enums are immutable. This list is not designed to be all inclusive, its intent is to demonstrate patterns that you can use to learn more about JavaScript enums. You can try these out using the following jsfiddle.
USE CASE 1 – Basic public enum function with no namespace protection. This use cases offers the least amount of protection against global variable leakage. I’d only expect to see this type of enum in very small, stand-alone applications.
function basicEnum() {
var values = {
BLACK: '#000000',
RED: '#FF0000',
GREEN: '#00FF00'
}
return values;
}
console.log("test0 " + basicEnum().BLACK); //test0 #000000
USE CASE 2 – Basic public enum that uses an internal, privately scoped namespace in which to define the enum object. This use case offers slightly more protection than Use Case 1, but the public function itself is still not protected within the global namespace. It’s possible there could be two functions with the same name “colorEnum”. And, the larger the application gets the higher probability there is of having accidental name duplication.
function colorEnum() {
var values = values || {}
values.colorEnum = {
BLACK: '#000000',
RED: '#FF0000',
GREEN: '#00FF00'
};
return values;
};
console.log("test1 " + colorEnum().colorEnum.GREEN); //test1 #00FF00
USE CASE 3 – Public enum with no namespace protection using an anonymous function expression to define the enum. This is a variation of Use Case 2 showing you can define multiple categories of enums. And, like Use Case 2 it still offers zero public/global namespace protection. I’m using the terms public and global to mean the same thing.
DoSomething = (function(){
var constValues = constValues || {}
constValues.color = {"BLACK" : "#000000" }
constValues.error = {"ERROR_TIMEOUT" : "Connection Timeout" }
return constValues;
});
console.log("test2 " + DoSomething().color.BLACK); //test2 #000000
console.log("test3 " + DoSomething().error.ERROR_TIMEOUT); //test3 Connection Timeout
USE CASE 4 – Public enum with namespace protection using an anonymous function expression and internal (private) namespaces to differentiate multiple categories of custom enum objects. This use case starts to offer better protection against global variable leakage by wrapping the public function in a namespace.
var my_test = my_test || {}
my_test.DoSomething = (function(){
var constValues = constValues || {}
constValues.color = {
"BLACK" : "#000000",
"RED" : "#FF0000"
}
constValues.error = {
"ERROR_TIMEOUT" : "Connection Timeout",
"ERROR_FAULT" : "Connection problem"
}
return constValues;
});
console.log("test4 " + my_test.DoSomething().color.BLACK); //test4 #000000
console.log("test5 " + my_test.DoSomething().error.ERROR_TIMEOUT); //test5 Connection Timeout
USE CASE 5 – Namespace protected public anonymous function expression along with an internally (private) scoped function that defines the enum. This is a variation of Use Case 5 showing how to use switch/case statements along with a privately scoped function that are all wrapped inside the anonymous function.
var my_second_test = my_second_test || {};
my_second_test.DoSomething = (function(val){
var color;
switch(val)
{
case 1:
color = basicEnum().BLACK;
break;
case 2:
color = basicEnum().RED;
break;
case 3:
color = basicEnum().GREEN;
break;
}
function basicEnum() {
var values = {
BLACK: '#000000',
RED: '#FF0000',
GREEN: '#00FF00'
}
return values;
}
return color;
});
console.log("test6 " + my_second_test.DoSomething(2)); //test6 #FF0000
USE CASE 6 – This Use Case shows placing a privately scoped anonymous function expression defining the enum inside a public anonymous function expression, and finally accessed through another prototyped anonymous function expression. Yikes, that was a lot of technical mumbo-jumbo verbiage, right?! It also offers several tests to validate if the enum is immutable or not.
var my_third_test = my_third_test || {};
my_third_test.DoSomething = (function(){
this.basicEnum = (function() {
var values = {
BLACK: '#000000',
RED: '#FF0000',
GREEN: '#00FF00'
}
return values;
});
});
my_third_test.DoSomething.prototype.findColor = (function(val){
var color;
switch(val)
{
case 1:
color = this.basicEnum().BLACK;
break;
case 2:
color = this.basicEnum().RED;
break;
case 3:
color = this.basicEnum().GREEN;
break;
}
return color;
});
var myColor = new my_third_test.DoSomething();
console.log("test7 " + myColor.findColor(3)); //test7 #00FF00
//The enum properties cannot be changed
myColor.basicEnum().BLACK = "test";
console.log("test8 " + myColor.basicEnum().BLACK); //test8 #000000
try{
//test that basicEnum() is immutable
myColor.basicEnum() = "test"; //Throws ERROR!
}
catch(err){
console.log("test9 " + err);
//test9 ReferenceError: Invalid left-hand side in assignment
}
Conclusion. So that’s all there is to it. Hopefully these examples help to not only demonstrate some patterns to protect your data, but also give you ideas for saving time while building larger applications.
Reference.
JSFiddle project