Would the following make the objects fulfil all characteristics that enums have in JavaScript? Something like:
my.namespace.ColorEnum = {
RED : 0,
GREEN : 1,
BLUE : 2
}
// later on
if(currentColor == my.namespace.ColorEnum.RED) {
// whatever
}
Or is there some other way I can do this?
0
as an enumeration number. Unless it's used for something that has not been set. JS treats false || undefined || null || 0 || "" || '' || NaN
all as the same value when compared using ==
.
0 == null
returns false
false == 0
and +null == 0
(and conversions to numbers happen sometimes when you don't expect it), while null == undefined
too, and +undefined
is NaN
(though NaN != NaN
).
Since 1.8.5 it's possible to seal and freeze the object, so define the above as:
const DaysEnum = Object.freeze({"monday":1, "tuesday":2, "wednesday":3, ...})
or
const DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Object.freeze(DaysEnum)
and voila! JS enums.
However, this doesn't prevent you from assigning an undesired value to a variable, which is often the main goal of enums:
let day = DaysEnum.tuesday
day = 298832342 // goes through without any errors
One way to ensure a stronger degree of type safety (with enums or otherwise) is to use a tool like TypeScript or Flow.
Quotes aren't needed but I kept them for consistency.
This isn't much of an answer, but I'd say that works just fine, personally
Having said that, since it doesn't matter what the values are (you've used 0, 1, 2), I'd use a meaningful string in case you ever wanted to output the current value.
Object.freeze()
. This will prevent other code from changing the enum's values. Example: var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2});
instanceof
checks. For example ColorEnum.RED instanceof ColorEnum
(returns true
). You can also resolve an instance out of a name ColorEnum.fromName("RED") === ColorEnum.RED
(returns true
). Each instance also has a .name()
and a .ordinal()
method, and the enum itself has a values()
method that returnd an array of all constants.
ToString()
method. We JS devs are already way too reliant on things "just working"! Also, one should be able to quickly switch
on an enum. Comparing strings is slower than comparing numbers, so you'll get slightly worse switch
performance if you use strings instead of integers.
UPDATE
I don't think my answer below is the best way to write enums in JavaScript anymore. See my blog post for more details: Enums in JavaScript.
Alerting the name is already possible:
if (currentColor == my.namespace.ColorEnum.RED) {
// alert name of currentColor (RED: 0)
var col = my.namespace.ColorEnum;
for (var name in col) {
if (col[name] == col.RED)
alert(name);
}
}
Alternatively, you could make the values objects, so you can have the cake and eat it too:
var SIZE = {
SMALL : {value: 0, name: "Small", code: "S"},
MEDIUM: {value: 1, name: "Medium", code: "M"},
LARGE : {value: 2, name: "Large", code: "L"}
};
var currentSize = SIZE.MEDIUM;
if (currentSize == SIZE.MEDIUM) {
// this alerts: "1: Medium"
alert(currentSize.value + ": " + currentSize.name);
}
In JavaScript, as it is a dynamic language, it is even possible to add enum values to the set later:
// Add EXTRALARGE size
SIZE.EXTRALARGE = {value: 3, name: "Extra Large", code: "XL"};
Remember, the fields of the enum (value, name and code in this example) are not needed for the identity check and are only there for convenience. Also the name of the size property itself does not need to be hard coded, but can also be set dynamically. So supposing you only know the name for your new enum value, you can still add it without problems:
// Add 'Extra Large' size, only knowing it's name
var name = "Extra Large";
SIZE[name] = {value: -1, name: name, code: "?"};
Of course this means that some assumptions can no longer be made (that value represents the correct order for the size for example).
Remember, in JavaScript an object is just like a map or hash table. A set of name-value pairs. You can loop through them or otherwise manipulate them without knowing much about them in advance.
Example
for (var sz in SIZE) {
// sz will be the names of the objects in SIZE, so
// 'SMALL', 'MEDIUM', 'LARGE', 'EXTRALARGE'
var size = SIZE[sz]; // Get the object mapped to the name in sz
for (var prop in size) {
// Get all the properties of the size object, iterates over
// 'value', 'name' and 'code'. You can inspect everything this way.
}
}
And by the way, if you are interested in namespaces, you may want to have a look at my solution for simple but powerful namespace and dependency management for JavaScript: Packages JS
Bottom line: You can't.
You can fake it, but you won't get type safety. Typically this is done by creating a simple dictionary of string values mapped to integer values. For example:
var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Document.Write("Enumerant: " + DaysEnum.tuesday);
The problem with this approach? You can accidentally redefine your enumerant, or accidentally have duplicate enumerant values. For example:
DaysEnum.monday = 4; // whoops, monday is now thursday, too
Edit
What about Artur Czajka's Object.freeze? Wouldn't that work to prevent you from setting monday to thursday? – Fry Quad
Absolutely, Object.freeze
would totally fix the problem I complained about. I would like to remind everyone that when I wrote the above, Object.freeze
didn't really exist.
Now.... now it opens up some very interesting possibilities.
Edit 2 Here's a very good library for creating enums.
http://www.2ality.com/2011/10/enums.html
While it probably doesn't fit every valid use of enums, it goes a very long way.
var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1
Here's what we all want:
function Enum(constantsList) {
for (var i in constantsList) {
this[constantsList[i]] = i;
}
}
Now you can create your enums:
var YesNo = new Enum(['NO', 'YES']);
var Color = new Enum(['RED', 'GREEN', 'BLUE']);
By doing this, constants can be acessed in the usual way (YesNo.YES, Color.GREEN) and they get a sequential int value (NO = 0, YES = 1; RED = 0, GREEN = 1, BLUE = 2).
You can also add methods, by using Enum.prototype:
Enum.prototype.values = function() {
return this.allValues;
/* for the above to work, you'd need to do
this.allValues = constantsList at the constructor */
};
Edit - small improvement - now with varargs: (unfortunately it doesn't work properly on IE :S... should stick with previous version then)
function Enum() {
for (var i in arguments) {
this[arguments[i]] = i;
}
}
var YesNo = new Enum('NO', 'YES');
var Color = new Enum('RED', 'GREEN', 'BLUE');
In most modern browsers, there is a symbol primitive data type which can be used to create an enumeration. It will ensure type safety of the enum as each symbol value is guaranteed by JavaScript to be unique, i.e. Symbol() != Symbol()
. For example:
const COLOR = Object.freeze({RED: Symbol(), BLUE: Symbol()});
To simplify debugging, you can add a description to enum values:
const COLOR = Object.freeze({RED: Symbol("RED"), BLUE: Symbol("BLUE")});
On GitHub you can find a wrapper that simplifies the code required to initialize the enum:
const color = new Enum("RED", "BLUE")
color.RED.toString() // Symbol(RED)
color.getName(color.RED) // RED
color.size // 2
color.values() // Symbol(RED), Symbol(BLUE)
color.toString() // RED,BLUE
Symbol
is intended for.
Object.freeze
only for people who haven't accepted the fact that "monkeypatch at your own risk" is the social contract of JS?
𝗦𝗲𝗹𝗳-𝗗𝗲𝘀𝗰𝗿𝗶𝗽𝘁𝗶𝘃𝗲 𝗘𝘅𝘁𝗲𝗻𝘀𝗶𝗯𝗹𝗲 𝗩𝗮𝗿𝗶𝗮𝗯𝗹𝗲 𝗡𝗮𝗺𝗲𝘀
Let's cut straight to the problem: file size. Every other answer listed here bloats your minified code to the extreme. I present to you that for the best possible reduction in code size by minification, performance, readability of code, large scale project management, and syntax hinting in many code editors, this is the correct way to do enumerations: underscore-notation variables.
As demonstrated in the chart above and example below, here are five easy steps to get started:
Determine a name for the enumeration group. Think of a noun that can describe the purpose of the enumeration or at least the entries in the enumeration. For example, a group of enumerations representing colors choosable by the user might be better named COLORCHOICES than COLORS. Decide whether enumerations in the group are mutually-exclusive or independent. If mutually-exclusive, start each enumerated variable name with ENUM_. If independent or side-by-side, use INDEX_. For each entry, create a new local variable whose name starts with ENUM_ or INDEX_, then the name of the group, then an underscore, then a unique friendly name for the property Add a ENUMLENGTH_, ENUMLEN_, INDEXLENGTH_, or INDEXLEN_ (whether LEN_ or LENGTH_ is personal preference) enumerated variable at the very end. You should use this variable wherever possible in your code to ensure that adding an extra entry to the enumeration and incrementing this value won't break your code. Give each successive enumerated variable a value one more than the last, starting at 0. There are comments on this page that say 0 should not be used as an enumerated value because 0 == null, 0 == false, 0 == "", and other JS craziness. I submit to you that, to avoid this problem and boost performance at the same time, always use === and never let == appear in your code except with typeof (e.x. typeof X == "string"). In all my years of using ===, I have never once had a problem with using 0 as an enumeration value. If you are still squeamish, then 1 could be used as the starting value in ENUM_ enumerations (but not in INDEX_ enumerations) without performance penalty in many cases.
const ENUM_COLORENUM_RED = 0;
const ENUM_COLORENUM_GREEN = 1;
const ENUM_COLORENUM_BLUE = 2;
const ENUMLEN_COLORENUM = 3;
// later on
if(currentColor === ENUM_COLORENUM_RED) {
// whatever
}
Here is how I remember when to use INDEX_
and when to use ENUM_
:
// Precondition: var arr = []; //
arr[INDEX_] = ENUM_;
However, ENUM_
can, in certain circumstances, be appropriate as an index such as when counting the occurrences of each item.
const ENUM_PET_CAT = 0,
ENUM_PET_DOG = 1,
ENUM_PET_RAT = 2,
ENUMLEN_PET = 3;
var favoritePets = [ENUM_PET_CAT, ENUM_PET_DOG, ENUM_PET_RAT,
ENUM_PET_DOG, ENUM_PET_DOG, ENUM_PET_CAT,
ENUM_PET_RAT, ENUM_PET_CAT, ENUM_PET_DOG];
var petsFrequency = [];
for (var i=0; i
Observe that, in the code above, it's really easy to add in a new kind of pet: you would just have to append a new entry after
𝗘𝘅𝘁𝗲𝗻𝗱 𝗨𝗽𝗽𝗲𝗿𝗰𝗮𝘀𝗲 𝗩𝗮𝗿𝗶𝗮𝗯𝗹𝗲𝘀 𝗪𝗶𝘁𝗵 𝗔𝗱𝗱𝗶𝘁𝗶𝗼𝗻
Additionally, this syntax of enumerations allows for clear and concise class extending as seen below. To extend a class, add an incrementing number to the
https://i.stack.imgur.com/xIHxl.png
(function(window){
"use strict";
var parseInt = window.parseInt;
// use INDEX_ when representing the index in an array instance
const INDEX_PIXELCOLOR_TYPE = 0, // is a ENUM_PIXELTYPE
INDEXLEN_PIXELCOLOR = 1,
INDEX_SOLIDCOLOR_R = INDEXLEN_PIXELCOLOR+0,
INDEX_SOLIDCOLOR_G = INDEXLEN_PIXELCOLOR+1,
INDEX_SOLIDCOLOR_B = INDEXLEN_PIXELCOLOR+2,
INDEXLEN_SOLIDCOLOR = INDEXLEN_PIXELCOLOR+3,
INDEX_ALPHACOLOR_R = INDEXLEN_PIXELCOLOR+0,
INDEX_ALPHACOLOR_G = INDEXLEN_PIXELCOLOR+1,
INDEX_ALPHACOLOR_B = INDEXLEN_PIXELCOLOR+2,
INDEX_ALPHACOLOR_A = INDEXLEN_PIXELCOLOR+3,
INDEXLEN_ALPHACOLOR = INDEXLEN_PIXELCOLOR+4,
// use ENUM_ when representing a mutually-exclusive species or type
ENUM_PIXELTYPE_SOLID = 0,
ENUM_PIXELTYPE_ALPHA = 1,
ENUM_PIXELTYPE_UNKNOWN = 2,
ENUMLEN_PIXELTYPE = 2;
function parseHexColor(inputString) {
var rawstr = inputString.trim().substring(1);
var result = [];
if (rawstr.length === 8) {
result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
result[INDEX_ALPHACOLOR_R] = parseInt(rawstr.substring(0,2), 16);
result[INDEX_ALPHACOLOR_G] = parseInt(rawstr.substring(2,4), 16);
result[INDEX_ALPHACOLOR_B] = parseInt(rawstr.substring(4,6), 16);
result[INDEX_ALPHACOLOR_A] = parseInt(rawstr.substring(4,6), 16);
} else if (rawstr.length === 4) {
result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
result[INDEX_ALPHACOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
result[INDEX_ALPHACOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
result[INDEX_ALPHACOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
result[INDEX_ALPHACOLOR_A] = parseInt(rawstr[3], 16) * 0x11;
} else if (rawstr.length === 6) {
result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
result[INDEX_SOLIDCOLOR_R] = parseInt(rawstr.substring(0,2), 16);
result[INDEX_SOLIDCOLOR_G] = parseInt(rawstr.substring(2,4), 16);
result[INDEX_SOLIDCOLOR_B] = parseInt(rawstr.substring(4,6), 16);
} else if (rawstr.length === 3) {
result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
result[INDEX_SOLIDCOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
result[INDEX_SOLIDCOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
result[INDEX_SOLIDCOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
} else {
result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_UNKNOWN;
}
return result;
}
// the red component of green
console.log(parseHexColor("#0f0")[INDEX_SOLIDCOLOR_R]);
// the alpha of transparent purple
console.log(parseHexColor("#f0f7")[INDEX_ALPHACOLOR_A]);
// the enumerated array for turquoise
console.log(parseHexColor("#40E0D0"));
})(self);
(Length: 2,450 bytes)
Some may say that this is less practical than other solutions: it wastes tons of space, it takes a long time to write, and it is not coated with sugar syntax. Those people would be right if they do not minify their code. However, no reasonable person would leave unminified code in the end product. For this minification, Closure Compiler is the best I have yet to find. Online access can be found here. Closure compiler is able to take all of this enumeration data and inline it, making your Javascript be super duper small and run super duper fast. Thus, Minify with Closure Compiler. Observe.
𝗠𝗶𝗻𝗶𝗳𝘆 𝗪𝗶𝘁𝗵 𝗖𝗹𝗼𝘀𝘂𝗿𝗲 𝗖𝗼𝗺𝗽𝗶𝗹𝗲𝗿
Closure compiler is able to perform some pretty incredible optimizations via inferences that are way beyond the capacities of any other Javascript minifier. Closure Compiler is able to inline primitive variables set to a fixed value. Closure Compiler is also able to make inferences based upon these inlined values and eliminate unused blocks in if-statements and loops.
https://i.stack.imgur.com/2cadt.jpg
'use strict';(function(e){function d(a){a=a.trim().substring(1);var b=[];8===a.length?(b[0]=1,b[1]=c(a.substring(0,2),16),b[2]=c(a.substring(2,4),16),b[3]=c(a.substring(4,6),16),b[4]=c(a.substring(4,6),16)):4===a.length?(b[1]=17*c(a[0],16),b[2]=17*c(a[1],16),b[3]=17*c(a[2],16),b[4]=17*c(a[3],16)):6===a.length?(b[0]=0,b[1]=c(a.substring(0,2),16),b[2]=c(a.substring(2,4),16),b[3]=c(a.substring(4,6),16)):3===a.length?(b[0]=0,b[1]=17*c(a[0],16),b[2]=17*c(a[1],16),b[3]=17*c(a[2],16)):b[0]=2;return b}var c=
e.parseInt;console.log(d("#0f0")[1]);console.log(d("#f0f7")[4]);console.log(d("#40E0D0"))})(self);
(Length: 605 bytes)
Closure Compiler rewards you for coding smarter and organizing your code well because, whereas many minifiers punish organized code with a bigger minified file size, Closure Compiler is able to sift through all your cleanliness and sanity to output an even smaller file size if you use tricks like variable name enumerations. That, in this one mind, is the holy grail of coding: a tool that both assists your code with a smaller minified size and assists your mind by training better programming habits.
𝗦𝗺𝗮𝗹𝗹𝗲𝗿 𝗖𝗼𝗱𝗲 𝗦𝗶𝘇𝗲
Now, let us see how big the equivalent file would be without any of these enumerations.
Source Without Using Enumerations (length: 1,973 bytes (477 bytes shorter than enumerated code!))
https://i.stack.imgur.com/DX0nA.png
As seen, without enumerations, the source code is shorter at the cost of a larger minified code. I do not know about you; but I know for sure that I do not incorporate source code into the end product. Thus, this form of enumerations is far superior insomuch that it results in smaller minified file sizes.
𝗖𝗼𝗼𝗽𝗲𝗿𝗮𝘁𝗶𝘃𝗲 🤝 𝗕𝘂𝗴 𝗙𝗶𝘅𝗶𝗻𝗴
Another advantage about this form of enumeration is that it can be used to easily manage large scale projects without sacrificing minified code size. When working on a large project with lots of other people, it might be beneficial to explicitly mark and label the variable names with who created the code so that the original creator of the code can be quickly identified for collaborative bug fixing.
𝗦𝘂𝗽𝗲𝗿𝗶𝗼𝗿 𝗣𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲
Further, this form of enumeration is also much faster after minification. In normal named properties, the browser has to use hashmaps to look up where the property is on the object. Although JIT compilers intelligently cache this location on the object, there is still tremendous overhead due to special cases such as deleting a lower property from the object.
But, with continuous non-sparse integer-indexed PACKED_ELEMENTS arrays, the browser is able to skip much of that overhead because the index of the value in the internal array is already specified. Yes, according to the ECMAScript standard, all properties are supposed to be treated as strings. Nevertheless, this aspect of the ECMAScript standard is very misleading about performance because all browsers have special optimizations for numeric indexes in arrays.
Compare the code above to the code below.
One might object to the code with enumerations seeming to be much longer than the code with ordinary objects, but looks can be deceiving. It is important to remember that source code size is not proportional to output size when using the epic Closure Compiler. Observe.
The minified code without enumerations is above and the minified code with enumerations is below.
The example above demonstrates that, in addition to having superior performance, the enumerated code also results in a smaller minified file size.
𝗘𝗮𝘀𝘆 𝗗𝗲𝗯𝘂𝗴𝗴𝗶𝗻𝗴
Furthermore, this one's personal cherry on the top is using this form of enumerations along with the CodeMirror text editor in Javascript mode. CodeMirror's Javascript syntax highlighting mode highlights local variables in the current scope. That way, you know instantly when you type in a variable name correctly because if the variable name was previously declared with the
https://i.stack.imgur.com/sSyEB.png
const ENUM_COLORENUM_RED = 0,
ENUM_COLORENUM_GREEN = 1,
ENUM_COLORENUM_BLUE = 2,
ENUMLEN_COLORENUM = 3;
var currentColor = ENUM_COLORENUM_GREEN;
if(currentColor === ENUM_COLORENUM_RED) {
// whatever
}
if(currentColor === ENUM_COLORENUM_DNE) {
// whatever
}
In the above snippet, you were alerted with an error because
𝗖𝗼𝗻𝗰𝗹𝘂𝘀𝗶𝗼𝗻
I think its safe to say that this methodology of enumeration is indeed the best way to go not just for minified code size, but also for performance, debugging, and collaboration.
ENUM_PET_RAT
and update ENUMLEN_PET
accordingly. It might be more difficult and buggy to add a new entry in other systems of enumeration.
LEN_
entry of the parent class. Then, finish out the subclass with its own LEN_
entry so that the subclass may be extended further in the future.
Minified Without Using Enumerations (length: 843 bytes (238 bytes longer than enumerated code))
// JG = Jack Giffin
const ENUM_JG_COLORENUM_RED = 0,
ENUM_JG_COLORENUM_GREEN = 1,
ENUM_JG_COLORENUM_BLUE = 2,
ENUMLEN_JG_COLORENUM = 3;
// later on
if(currentColor === ENUM_JG_COLORENUM_RED) {
// whatever
}
// PL = Pepper Loftus
// BK = Bob Knight
const ENUM_PL_ARRAYTYPE_UNSORTED = 0,
ENUM_PL_ARRAYTYPE_ISSORTED = 1,
ENUM_BK_ARRAYTYPE_CHUNKED = 2, // added by Bob Knight
ENUM_JG_ARRAYTYPE_INCOMPLETE = 3, // added by jack giffin
ENUMLEN_PL_COLORENUM = 4;
// later on
if(
randomArray === ENUM_PL_ARRAYTYPE_UNSORTED ||
randomArray === ENUM_BK_ARRAYTYPE_CHUNKED
) {
// whatever
}
/// Hashmaps are slow, even with JIT juice
var ref = {};
ref.count = 10;
ref.value = "foobar";
/// Arrays, however, are always lightning fast
const INDEX_REFERENCE_COUNT = 0;
const INDEX_REFERENCE_VALUE = 1;
const INDEXLENGTH_REFERENCE = 2;
var ref = [];
ref[INDEX_REFERENCE_COUNT] = 10;
ref[INDEX_REFERENCE_VALUE] = "foobar";
/// Hashmaps are slow, even with JIT juice
var a={count:10,value:"foobar"};
/// Arrays, however, are always lightning fast
var a=[10,"foobar"];
var
keyword, then the variable name turns a special color (cyan by default). Even if you do not use CodeMirror, then at least the browser throws a helpful [variable name] is not defined
exception when executing code with mistyped enumeration names. Also, JavaScript tools such as JSLint and Closure Compiler are very loud about telling you when you mistype in an enumeration variable name. CodeMirror, the browser, and various Javascript tools put together make debugging this form of enumeration very simple and really easy.
ENUM_COLORENUM_DNE
does not exist.
colors.REED
yields undefined
), so typos create elusive conundrums. YEA does not distinguish between usage of enumerations as indexes and IDs, leading to confusing code where everything looks the same. …
Use Javascript Proxies
TLDR: Add this class to your utility methods and use it throughout your code, it mocks Enum behavior from traditional programming languages, and actually throws errors when you try to either access an enumerator that does not exist or add/update an enumerator. No need to rely on Object.freeze()
.
class Enum {
constructor(enumObj) {
const handler = {
get(target, name) {
if (typeof target[name] != 'undefined') {
return target[name];
}
throw new Error(`No such enumerator: ${name}`);
},
set() {
throw new Error('Cannot add/update properties on an Enum instance after it is defined')
}
};
return new Proxy(enumObj, handler);
}
}
Then create enums by instantiating the class:
const roles = new Enum({
ADMIN: 'Admin',
USER: 'User',
});
Full Explanation:
One very beneficial feature of Enums that you get from traditional languages is that they blow up (throw a compile-time error) if you try to access an enumerator which does not exist.
Besides freezing the mocked enum structure to prevent additional values from accidentally/maliciously being added, none of the other answers address that intrinsic feature of Enums.
As you are probably aware, accessing non-existing members in JavaScript simply returns undefined
and does not blow up your code. Since enumerators are predefined constants (i.e. days of the week), there should never be a case when an enumerator should be undefined.
Don't get me wrong, JavaScript's behavior of returning undefined
when accessing undefined properties is actually a very powerful feature of language, but it's not a feature you want when you are trying to mock traditional Enum structures.
This is where Proxy objects shine. Proxies were standardized in the language with the introduction of ES6 (ES2015). Here's the description from MDN:
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Similar to a web server proxy, JavaScript proxies are able to intercept operations on objects (with the use of "traps", call them hooks if you like) and allow you to perform various checks, actions and/or manipulations before they complete (or in some cases stopping the operations altogether which is exactly what we want to do if and when we try to reference an enumerator which does not exist).
Here's a contrived example that uses the Proxy object to mimic Enums. The enumerators in this example are standard HTTP Methods (i.e. "GET", "POST", etc.):
// Class for creating enums (13 lines) // Feel free to add this to your utility library in // your codebase and profit! Note: As Proxies are an ES6 // feature, some browsers/clients may not support it and // you may need to transpile using a service like babel class Enum { // The Enum class instantiates a JavaScript Proxy object. // Instantiating a `Proxy` object requires two parameters, // a `target` object and a `handler`. We first define the handler, // then use the handler to instantiate a Proxy. // A proxy handler is simply an object whose properties // are functions which define the behavior of the proxy // when an operation is performed on it. // For enums, we need to define behavior that lets us check what enumerator // is being accessed and what enumerator is being set. This can be done by // defining "get" and "set" traps. constructor(enumObj) { const handler = { get(target, name) { if (typeof target[name] != 'undefined') { return target[name] } throw new Error(`No such enumerator: ${name}`) }, set() { throw new Error('Cannot add/update properties on an Enum instance after it is defined') } } // Freeze the target object to prevent modifications return new Proxy(enumObj, handler) } } // Now that we have a generic way of creating Enums, lets create our first Enum! const httpMethods = new Enum({ DELETE: "DELETE", GET: "GET", OPTIONS: "OPTIONS", PATCH: "PATCH", POST: "POST", PUT: "PUT" }) // Sanity checks console.log(httpMethods.DELETE) // logs "DELETE" try { httpMethods.delete = "delete" } catch (e) { console.log("Error: ", e.message) } // throws "Cannot add/update properties on an Enum instance after it is defined" try { console.log(httpMethods.delete) } catch (e) { console.log("Error: ", e.message) } // throws "No such enumerator: delete"
ASIDE: What the heck is a proxy?
I remember when I first started seeing the word proxy everywhere, it definitely didn't make sense to me for a long time. If that's you right now, I think an easy way to generalize proxies is to think of them as software, institutions, or even people that act as intermediaries or middlemen between two servers, companies, or people.
valueOf
method by specifying it as instance method on the Enum class. However, why do you want to access it this way versus just accessing it via dot notation?
logLevelEnum[settings.get("log_level")]
? adding parseOrThrow
would just be repetitive to what the proxy traps are already doing for you.
I've been playing around with this, as I love my enums. =)
Using Object.defineProperty
I think I came up with a somewhat viable solution.
Here's a jsfiddle: http://jsfiddle.net/ZV4A6/
Using this method.. you should (in theory) be able to call and define enum values for any object, without affecting other attributes of that object.
Object.defineProperty(Object.prototype,'Enum', {
value: function() {
for(i in arguments) {
Object.defineProperty(this,arguments[i], {
value:parseInt(i),
writable:false,
enumerable:true,
configurable:true
});
}
return this;
},
writable:false,
enumerable:false,
configurable:false
});
Because of the attribute writable:false
this should make it type safe.
So you should be able to create a custom object, then call Enum()
on it. The values assigned start at 0 and increment per item.
var EnumColors={};
EnumColors.Enum('RED','BLUE','GREEN','YELLOW');
EnumColors.RED; // == 0
EnumColors.BLUE; // == 1
EnumColors.GREEN; // == 2
EnumColors.YELLOW; // == 3
return this;
at the end of Enum you could do : var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW');
false
is the default for writable
, enumerable
and configurable
. No need for chewing over defaults.
This is an old one I know, but the way it has since been implemented via the TypeScript interface is:
var MyEnum;
(function (MyEnum) {
MyEnum[MyEnum["Foo"] = 0] = "Foo";
MyEnum[MyEnum["FooBar"] = 2] = "FooBar";
MyEnum[MyEnum["Bar"] = 1] = "Bar";
})(MyEnum|| (MyEnum= {}));
This enables you to look up on both MyEnum.Bar
which returns 1, and MyEnum[1]
which returns "Bar" regardless of the order of declaration.
enum MyEnum { Foo, Bar, Foobar }
In ES7 , you can do an elegant ENUM relying on static attributes:
class ColorEnum {
static RED = 0 ;
static GREEN = 1;
static BLUE = 2;
}
then
if (currentColor === ColorEnum.GREEN ) {/*-- coding --*/}
The advantage ( of using class instead of literal object) is to have a parent class Enum
then all your Enums will extends that class.
class ColorEnum extends Enum {/*....*/}
new ColorEnum()
makes absolutely no sense.
Enum
has standard static enumerator methods on it, like getValues()
, getNames()
, iterate()
, etc. If that's the case, you don't have to reimplement them for each new kind of enum
.
Create an object literal:
const Modes = {
DRAGGING: 'drag',
SCALING: 'scale',
CLICKED: 'click'
};
const
doesn't make the object's properties immutable, it only means that the variable Modes
can't be reassigned to something else. To make it more complete, use Object.freeze()
alongside const
.
Object.freeze
. It prevent Closure Compiler from inlining the object.
This is the solution that I use.
function Enum() {
this._enums = [];
this._lookups = {};
}
Enum.prototype.getEnums = function() {
return _enums;
}
Enum.prototype.forEach = function(callback){
var length = this._enums.length;
for (var i = 0; i < length; ++i){
callback(this._enums[i]);
}
}
Enum.prototype.addEnum = function(e) {
this._enums.push(e);
}
Enum.prototype.getByName = function(name) {
return this[name];
}
Enum.prototype.getByValue = function(field, value) {
var lookup = this._lookups[field];
if(lookup) {
return lookup[value];
} else {
this._lookups[field] = ( lookup = {});
var k = this._enums.length - 1;
for(; k >= 0; --k) {
var m = this._enums[k];
var j = m[field];
lookup[j] = m;
if(j == value) {
return m;
}
}
}
return null;
}
function defineEnum(definition) {
var k;
var e = new Enum();
for(k in definition) {
var j = definition[k];
e[k] = j;
e.addEnum(j)
}
return e;
}
And you define your enums like this:
var COLORS = defineEnum({
RED : {
value : 1,
string : 'red'
},
GREEN : {
value : 2,
string : 'green'
},
BLUE : {
value : 3,
string : 'blue'
}
});
And this is how you access your enums:
COLORS.BLUE.string
COLORS.BLUE.value
COLORS.getByName('BLUE').string
COLORS.getByValue('value', 1).string
COLORS.forEach(function(e){
// do what you want with e
});
I usually use the last 2 methods for mapping enums from message objects.
Some advantages to this approach:
Easy to declare enums
Easy to access your enums
Your enums can be complex types
The Enum class has some associative caching if you are using getByValue a lot
Some disadvantages:
Some messy memory management going on in there, as I keep the references to the enums
Still no type safety
If you're using Backbone, you can get full-blown enum functionality (find by id, name, custom members) for free using Backbone.Collection.
// enum instance members, optional
var Color = Backbone.Model.extend({
print : function() {
console.log("I am " + this.get("name"))
}
});
// enum creation
var Colors = new Backbone.Collection([
{ id : 1, name : "Red", rgb : 0xFF0000},
{ id : 2, name : "Green" , rgb : 0x00FF00},
{ id : 3, name : "Blue" , rgb : 0x0000FF}
], {
model : Color
});
// Expose members through public fields.
Colors.each(function(color) {
Colors[color.get("name")] = color;
});
// using
Colors.Red.print()
your answers are far too complicated
var buildSet = function(array) {
var set = {};
for (var i in array) {
var item = array[i];
set[item] = item;
}
return set;
}
var myEnum = buildSet(['RED','GREEN','BLUE']);
// myEnum.RED == 'RED' ...etc
I've modified the solution of Andre 'Fi':
function Enum() {
var that = this;
for (var i in arguments) {
that[arguments[i]] = i;
}
this.name = function(value) {
for (var key in that) {
if (that[key] == value) {
return key;
}
}
};
this.exist = function(value) {
return (typeof that.name(value) !== "undefined");
};
if (Object.freeze) {
Object.freeze(that);
}
}
Test:
var Color = new Enum('RED', 'GREEN', 'BLUE');
undefined
Color.name(Color.REDs)
undefined
Color.name(Color.RED)
"RED"
Color.exist(Color.REDs)
false
Color.exist(Color.RED)
true
I came up with this approach which is modeled after enums in Java. These are type-safe, and so you can perform instanceof
checks as well.
You can define enums like this:
var Days = Enum.define("Days", ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]);
Days
now refers to the Days
enum:
Days.Monday instanceof Days; // true
Days.Friday.name(); // "Friday"
Days.Friday.ordinal(); // 4
Days.Sunday === Days.Sunday; // true
Days.Sunday === Days.Friday; // false
Days.Sunday.toString(); // "Sunday"
Days.toString() // "Days { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } "
Days.values().map(function(e) { return e.name(); }); //["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
Days.values()[4].name(); //"Friday"
Days.fromName("Thursday") === Days.Thursday // true
Days.fromName("Wednesday").name() // "Wednesday"
Days.Friday.fromName("Saturday").name() // "Saturday"
The implementation:
var Enum = (function () {
/**
* Function to define an enum
* @param typeName - The name of the enum.
* @param constants - The constants on the enum. Can be an array of strings, or an object where each key is an enum
* constant, and the values are objects that describe attributes that can be attached to the associated constant.
*/
function define(typeName, constants) {
/** Check Arguments **/
if (typeof typeName === "undefined") {
throw new TypeError("A name is required.");
}
if (!(constants instanceof Array) && (Object.getPrototypeOf(constants) !== Object.prototype)) {
throw new TypeError("The constants parameter must either be an array or an object.");
} else if ((constants instanceof Array) && constants.length === 0) {
throw new TypeError("Need to provide at least one constant.");
} else if ((constants instanceof Array) && !constants.reduce(function (isString, element) {
return isString && (typeof element === "string");
}, true)) {
throw new TypeError("One or more elements in the constant array is not a string.");
} else if (Object.getPrototypeOf(constants) === Object.prototype && !Object.keys(constants).reduce(function (isObject, constant) {
return Object.getPrototypeOf(constants[constant]) === Object.prototype;
}, true)) {
throw new TypeError("One or more constants do not have an associated object-value.");
}
var isArray = (constants instanceof Array);
var isObject = !isArray;
/** Private sentinel-object used to guard enum constructor so that no one else can create enum instances **/
function __() { };
/** Dynamically define a function with the same name as the enum we want to define. **/
var __enum = new Function(["__"],
"return function " + typeName + "(sentinel, name, ordinal) {" +
"if(!(sentinel instanceof __)) {" +
"throw new TypeError(\"Cannot instantiate an instance of " + typeName + ".\");" +
"}" +
"this.__name = name;" +
"this.__ordinal = ordinal;" +
"}"
)(__);
/** Private objects used to maintain enum instances for values(), and to look up enum instances for fromName() **/
var __values = [];
var __dict = {};
/** Attach values() and fromName() methods to the class itself (kind of like static methods). **/
Object.defineProperty(__enum, "values", {
value: function () {
return __values;
}
});
Object.defineProperty(__enum, "fromName", {
value: function (name) {
var __constant = __dict[name]
if (__constant) {
return __constant;
} else {
throw new TypeError(typeName + " does not have a constant with name " + name + ".");
}
}
});
/**
* The following methods are available to all instances of the enum. values() and fromName() need to be
* available to each constant, and so we will attach them on the prototype. But really, they're just
* aliases to their counterparts on the prototype.
*/
Object.defineProperty(__enum.prototype, "values", {
value: __enum.values
});
Object.defineProperty(__enum.prototype, "fromName", {
value: __enum.fromName
});
Object.defineProperty(__enum.prototype, "name", {
value: function () {
return this.__name;
}
});
Object.defineProperty(__enum.prototype, "ordinal", {
value: function () {
return this.__ordinal;
}
});
Object.defineProperty(__enum.prototype, "valueOf", {
value: function () {
return this.__name;
}
});
Object.defineProperty(__enum.prototype, "toString", {
value: function () {
return this.__name;
}
});
/**
* If constants was an array, we can the element values directly. Otherwise, we will have to use the keys
* from the constants object.
*/
var _constants = constants;
if (isObject) {
_constants = Object.keys(constants);
}
/** Iterate over all constants, create an instance of our enum for each one, and attach it to the enum type **/
_constants.forEach(function (name, ordinal) {
// Create an instance of the enum
var __constant = new __enum(new __(), name, ordinal);
// If constants was an object, we want to attach the provided attributes to the instance.
if (isObject) {
Object.keys(constants[name]).forEach(function (attr) {
Object.defineProperty(__constant, attr, {
value: constants[name][attr]
});
});
}
// Freeze the instance so that it cannot be modified.
Object.freeze(__constant);
// Attach the instance using the provided name to the enum type itself.
Object.defineProperty(__enum, name, {
value: __constant
});
// Update our private objects
__values.push(__constant);
__dict[name] = __constant;
});
/** Define a friendly toString method for the enum **/
var string = typeName + " { " + __enum.values().map(function (c) {
return c.name();
}).join(", ") + " } ";
Object.defineProperty(__enum, "toString", {
value: function () {
return string;
}
});
/** Freeze our private objects **/
Object.freeze(__values);
Object.freeze(__dict);
/** Freeze the prototype on the enum and the enum itself **/
Object.freeze(__enum.prototype);
Object.freeze(__enum);
/** Return the enum **/
return __enum;
}
return {
define: define
}
})();
freeze
method for backward compatibility? E.g., if (Object.freeze) { Object.freeze(values); }
var ColorEnum = {
red: {},
green: {},
blue: {}
}
You don't need to make sure you don't assign duplicate numbers to different enum values this way. A new object gets instantiated and assigned to all enum values.
IE8 does Not support freeze() method.
Source: http://kangax.github.io/compat-table/es5/, Click on "Show obsolete browsers?" on top, and check IE8 & freeze row col intersection.
In my current game project, I have used below, since few customers still use IE8:
var CONST_WILD_TYPES = {
REGULAR: 'REGULAR',
EXPANDING: 'EXPANDING',
STICKY: 'STICKY',
SHIFTING: 'SHIFTING'
};
We could also do:
var CONST_WILD_TYPES = {
REGULAR: 'RE',
EXPANDING: 'EX',
STICKY: 'ST',
SHIFTING: 'SH'
};
or even this:
var CONST_WILD_TYPES = {
REGULAR: '1',
EXPANDING: '2',
STICKY: '3',
SHIFTING: '4'
};
The last one, seems most efficient for string, it reduces your total bandwidth if you have server & client exchanging this data. Of course, now it's your duty to make sure there are no conflicts in the data (RE, EX, etc. must be unique, also 1, 2, etc. should be unique). Note that you need to maintain these forever for backward compatibility.
Assignment:
var wildType = CONST_WILD_TYPES.REGULAR;
Comparision:
if (wildType === CONST_WILD_TYPES.REGULAR) {
// do something here
}
I wasn't satisfied with any of the answers, so I made Yet Another Enum (YEA!).
This implementation:
uses more up-to-date JS
requires just the declaration of this one class to easily create enums
has mapping by name (colors.RED), string (colors["RED"]), and index (colors[0]), but you only need to pass in the strings as an array
binds equivalent toString() and valueOf() functions to each enum object (if this is somehow not desired, one can simply remove it - small overhead for JS though)
has optional global naming/storage by name string
freezes the enum object once created so that it can't be modified
Special thanks to Andre 'Fi''s answer for some inspiration.
The codes:
class Enums {
static create({ name = undefined, items = [] }) {
let newEnum = {};
newEnum.length = items.length;
newEnum.items = items;
for (let itemIndex in items) {
//Map by name.
newEnum[items[itemIndex]] = parseInt(itemIndex, 10);
//Map by index.
newEnum[parseInt(itemIndex, 10)] = items[itemIndex];
}
newEnum.toString = Enums.enumToString.bind(newEnum);
newEnum.valueOf = newEnum.toString;
//Optional naming and global registration.
if (name != undefined) {
newEnum.name = name;
Enums[name] = newEnum;
}
//Prevent modification of the enum object.
Object.freeze(newEnum);
return newEnum;
}
static enumToString() {
return "Enum " +
(this.name != undefined ? this.name + " " : "") +
"[" + this.items.toString() + "]";
}
}
Usage:
let colors = Enums.create({
name: "COLORS",
items: [ "RED", "GREEN", "BLUE", "PORPLE" ]
});
//Global access, if named.
Enums.COLORS;
colors.items; //Array(4) [ "RED", "GREEN", "BLUE", "PORPLE" ]
colors.length; //4
colors.RED; //0
colors.GREEN; //1
colors.BLUE; //2
colors.PORPLE; //3
colors[0]; //"RED"
colors[1]; //"GREEN"
colors[2]; //"BLUE"
colors[3]; //"PORPLE"
colors.toString(); //"Enum COLORS [RED,GREEN,BLUE,PORPLE]"
//Enum frozen, makes it a real enum.
colors.RED = 9001;
colors.RED; //0
Simplest solution:
Create
var Status = Object.freeze({
"Connecting":0,
"Ready":1,
"Loading":2,
"Processing": 3
});
Get Value
console.log(Status.Ready) // 1
Get Key
console.log(Object.keys(Status)[Status.Ready]) // Ready
es7 way, (iterator, freeze), usage:
const ThreeWiseMen = new Enum('Melchior', 'Caspar', 'Balthazar')
for (let name of ThreeWiseMen)
console.log(name)
// with a given key
let key = ThreeWiseMen.Melchior
console.log(key in ThreeWiseMen) // true (string conversion, also true: 'Melchior' in ThreeWiseMen)
for (let entry from key.enum)
console.log(entry)
// prevent alteration (throws TypeError in strict mode)
ThreeWiseMen.Me = 'Me too!'
ThreeWiseMen.Melchior.name = 'Foo'
code:
class EnumKey {
constructor(props) { Object.freeze(Object.assign(this, props)) }
toString() { return this.name }
}
export class Enum {
constructor(...keys) {
for (let [index, key] of keys.entries()) {
Object.defineProperty(this, key, {
value: new EnumKey({ name:key, index, enum:this }),
enumerable: true,
})
}
Object.freeze(this)
}
*[Symbol.iterator]() {
for (let key of Object.keys(this))
yield this[key]
}
toString() { return [...this].join(', ') }
}
This can be useful:
const [CATS, DOGS, BIRDS] = ENUM();
The implementation is simple and efficient:
function * ENUM(count=1) { while(true) yield count++ }
A generator can yield the exact sequence of integers required, without knowing how many constants there are. It can also support an optional argument that specifies which (possibly negative) number to start from (defaulting to 1
).
A quick and simple way would be :
var Colors = function(){
return {
'WHITE':0,
'BLACK':1,
'RED':2,
'GREEN':3
}
}();
console.log(Colors.WHITE) //this prints out "0"
Here's a couple different ways to implement TypeScript enums.
The easiest way is to just iterate over an object, adding inverted key-value pairs to the object. The only drawback is that you must manually set the value for each member.
function _enum(list) {
for (var key in list) {
list[list[key] = list[key]] = key;
}
return Object.freeze(list);
}
var Color = _enum({
Red: 0,
Green: 5,
Blue: 2
});
// Color → {0: "Red", 2: "Blue", 5: "Green", "Red": 0, "Green": 5, "Blue": 2}
// Color.Red → 0
// Color.Green → 5
// Color.Blue → 2
// Color[5] → Green
// Color.Blue > Color.Green → false
And here's a lodash mixin to create an enum using a string. While this version is a little bit more involved, it does the numbering automatically for you. All the lodash methods used in this example have a regular JavaScript equivalent, so you can easily switch them out if you want.
function enum() {
var key, val = -1, list = {};
_.reduce(_.toArray(arguments), function(result, kvp) {
kvp = kvp.split("=");
key = _.trim(kvp[0]);
val = _.parseInt(kvp[1]) || ++val;
result[result[val] = key] = val;
return result;
}, list);
return Object.freeze(list);
}
// Add enum to lodash
_.mixin({ "enum": enum });
var Color = _.enum(
"Red",
"Green",
"Blue = 5",
"Yellow",
"Purple = 20",
"Gray"
);
// Color.Red → 0
// Color.Green → 1
// Color.Blue → 5
// Color.Yellow → 6
// Color.Purple → 20
// Color.Gray → 21
// Color[5] → Blue
I've just published an NPM package gen_enum allows you to create Enum data structure in Javascript quickly:
var genEnum = require('gen_enum');
var AppMode = genEnum('SIGN_UP, LOG_IN, FORGOT_PASSWORD');
var curMode = AppMode.LOG_IN;
console.log(curMode.isLogIn()); // output true
console.log(curMode.isSignUp()); // output false
console.log(curMode.isForgotPassword()); // output false
One nice thing about this little tool is in modern environment (including nodejs and IE 9+ browsers) the returned Enum object is immutable.
For more information please checkout https://github.com/greenlaw110/enumjs
Updates
I obsolete gen_enum
package and merge the function into constjs package, which provides more features including immutable objects, JSON string deserialization, string constants and bitmap generation etc. Checkout https://www.npmjs.com/package/constjs for more information
To upgrade from gen_enum
to constjs
just change the statement
var genEnum = require('gen_enum');
to
var genEnum = require('constjs').enum;
I've made an Enum class that can fetch values AND names at O(1). It can also generate an Object Array containing all Names and Values.
function Enum(obj) {
// Names must be unique, Values do not.
// Putting same values for different Names is risky for this implementation
this._reserved = {
_namesObj: {},
_objArr: [],
_namesArr: [],
_valuesArr: [],
_selectOptionsHTML: ""
};
for (k in obj) {
if (obj.hasOwnProperty(k)) {
this[k] = obj[k];
this._reserved._namesObj[obj[k]] = k;
}
}
}
(function () {
this.GetName = function (val) {
if (typeof this._reserved._namesObj[val] === "undefined")
return null;
return this._reserved._namesObj[val];
};
this.GetValue = function (name) {
if (typeof this[name] === "undefined")
return null;
return this[name];
};
this.GetObjArr = function () {
if (this._reserved._objArr.length == 0) {
var arr = [];
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
arr.push({
Name: k,
Value: this[k]
});
}
this._reserved._objArr = arr;
}
return this._reserved._objArr;
};
this.GetNamesArr = function () {
if (this._reserved._namesArr.length == 0) {
var arr = [];
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
arr.push(k);
}
this._reserved._namesArr = arr;
}
return this._reserved._namesArr;
};
this.GetValuesArr = function () {
if (this._reserved._valuesArr.length == 0) {
var arr = [];
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
arr.push(this[k]);
}
this._reserved._valuesArr = arr;
}
return this._reserved._valuesArr;
};
this.GetSelectOptionsHTML = function () {
if (this._reserved._selectOptionsHTML.length == 0) {
var html = "";
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
html += "<option value='" + this[k] + "'>" + k + "</option>";
}
this._reserved._selectOptionsHTML = html;
}
return this._reserved._selectOptionsHTML;
};
}).call(Enum.prototype);
You can init'd it like this:
var enum1 = new Enum({
item1: 0,
item2: 1,
item3: 2
});
To fetch a value (like Enums in C#):
var val2 = enum1.item2;
To fetch a name for a value (can be ambiguous when putting the same value for different names):
var name1 = enum1.GetName(0); // "item1"
To get an array with each name & value in an object:
var arr = enum1.GetObjArr();
Will generate:
[{ Name: "item1", Value: 0}, { ... }, ... ]
You can also get the html select options readily:
var html = enum1.GetSelectOptionsHTML();
Which holds:
"<option value='0'>item1</option>..."
Even though only static methods (and not static properties) are supported in ES2015 (see here as well, §15.2.2.2), curiously you can use the below with Babel with the es2015
preset:
class CellState {
v: string;
constructor(v: string) {
this.v = v;
Object.freeze(this);
}
static EMPTY = new CellState('e');
static OCCUPIED = new CellState('o');
static HIGHLIGHTED = new CellState('h');
static values = function(): Array<CellState> {
const rv = [];
rv.push(CellState.EMPTY);
rv.push(CellState.OCCUPIED);
rv.push(CellState.HIGHLIGHTED);
return rv;
}
}
Object.freeze(CellState);
I found this to be working as expected even across modules (e.g. importing the CellState
enum from another module) and also when I import a module using Webpack.
The advantage this method has over most other answers is that you can use it alongside a static type checker (e.g. Flow) and you can assert, at development time using static type checking, that your variables, parameters, etc. are of the specific CellState
"enum" rather than some other enum (which would be impossible to distinguish if you used generic objects or symbols).
update
The above code has a deficiency in that it allows one to create additional objects of type CellState
(even though one can't assign them to the static fields of CellState
since it's frozen). Still, the below more refined code offers the following advantages:
no more objects of type CellState may be created you are guaranteed that no two enum instances are assigned the same code utility method to get the enum back from a string representation the values function that returns all instances of the enum does not have to create the return value in the above, manual (and error-prone) way. 'use strict'; class Status { constructor(code, displayName = code) { if (Status.INSTANCES.has(code)) throw new Error(`duplicate code value: [${code}]`); if (!Status.canCreateMoreInstances) throw new Error(`attempt to call constructor(${code}`+ `, ${displayName}) after all static instances have been created`); this.code = code; this.displayName = displayName; Object.freeze(this); Status.INSTANCES.set(this.code, this); } toString() { return `[code: ${this.code}, displayName: ${this.displayName}]`; } static INSTANCES = new Map(); static canCreateMoreInstances = true; // the values: static ARCHIVED = new Status('Archived'); static OBSERVED = new Status('Observed'); static SCHEDULED = new Status('Scheduled'); static UNOBSERVED = new Status('Unobserved'); static UNTRIGGERED = new Status('Untriggered'); static values = function() { return Array.from(Status.INSTANCES.values()); } static fromCode(code) { if (!Status.INSTANCES.has(code)) throw new Error(`unknown code: ${code}`); else return Status.INSTANCES.get(code); } } Status.canCreateMoreInstances = false; Object.freeze(Status); exports.Status = Status;
This is how Typescript translates it's enum
into Javascript:
var makeEnum = function(obj) {
obj[ obj['Active'] = 1 ] = 'Active';
obj[ obj['Closed'] = 2 ] = 'Closed';
obj[ obj['Deleted'] = 3 ] = 'Deleted';
}
Now:
makeEnum( NewObj = {} )
// => {1: "Active", 2: "Closed", 3: "Deleted", Active: 1, Closed: 2, Deleted: 3}
At first I was confused why obj[1]
returns 'Active'
, but then realised that its dead simple - Assignment operator assigns value and then returns it:
obj['foo'] = 1
// => 1
You can do something like this
var Enum = (function(foo) {
var EnumItem = function(item){
if(typeof item == "string"){
this.name = item;
} else {
this.name = item.name;
}
}
EnumItem.prototype = new String("DEFAULT");
EnumItem.prototype.toString = function(){
return this.name;
}
EnumItem.prototype.equals = function(item){
if(typeof item == "string"){
return this.name == item;
} else {
return this == item && this.name == item.name;
}
}
function Enum() {
this.add.apply(this, arguments);
Object.freeze(this);
}
Enum.prototype.add = function() {
for (var i in arguments) {
var enumItem = new EnumItem(arguments[i]);
this[enumItem.name] = enumItem;
}
};
Enum.prototype.toList = function() {
return Object.keys(this);
};
foo.Enum = Enum;
return Enum;
})(this);
var STATUS = new Enum("CLOSED","PENDING", { name : "CONFIRMED", ackd : true });
var STATE = new Enum("CLOSED","PENDING","CONFIRMED",{ name : "STARTED"},{ name : "PROCESSING"});
As defined in this library. https://github.com/webmodule/foo/blob/master/foo.js#L217
Complete example https://gist.github.com/lnt/bb13a2fd63cdb8bce85fd62965a20026
Success story sharing
var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });
. You don't need to specify an id, you can just use an empty object to compare enums.if (incommingEnum === DaysEnum.monday) //incommingEnum is monday
if (Object.freeze) { Object.freeze(DaysEnum); }
({ monday: {},
etc. means that if you convert that object to JSON via stringify you'll get[{"day": {}}]
which isn't gonna work.var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
. Comparing objects like in my previous comment is MUCH MORE SLOWER than comparing numbers.