2259

What is the preferred syntax for defining enums 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 a more preferable idiom?

DAIRAV
  • 701
  • 1
  • 6
  • 29
David Citron
  • 41,009
  • 18
  • 60
  • 71
  • 162
    Don't use `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 `==`. – matsko Jan 17 '15 at 18:10
  • 190
    @matsko isn't that just an argument against using ==? – sdm350 Feb 24 '15 at 21:40
  • 7
    `0 == null` returns false – mcont Apr 03 '15 at 14:58
  • 12
    But `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`). – sanderd17 May 30 '15 at 15:59
  • 67
    The double equality matrix is more confusing than microsoft word's auto-formatting – aaaaaa Mar 23 '16 at 20:32
  • 2
    This method you've mentioned in your question is quite useful for NodeJS. Replacing `my.namespace.ColorEnum = ` with `module.export = ` in a blank file gives you an enum in it's own NodeJS file. Brilliant. – Tim Visée Apr 12 '16 at 23:09
  • Why isn't the var keyword used before the object name? – Greener Jun 03 '16 at 21:49
  • 2
    @greener OP is showing how he is adding `ColorEnum` to the *existing* 'namespace' object `my.namespace`. – Stijn de Witt Jun 30 '16 at 00:40
  • 2
    You can even use symbols as the values of your keys to ensure uniqueness, see http://stackoverflow.com/questions/38993711/improve-enum-type-with-symbols-in-es2015/38993712#38993712 – Simone Poggi Aug 17 '16 at 10:12
  • 1
    @David Citron Feel free to change the accepted answer to https://stackoverflow.com/a/5040502/3492994 which is a better way to create enums in JavaScript. – Stefnotch Jan 10 '18 at 18:43
  • It doesn't really matter that much how you define enum constants. The main point of enum types is ensuring that a variable has a limited set of allowed values, and JS variables don't have any way of doing this. There *are* standard syntaxes for defining enum types in TypeScript and Flow, though that's different from putting your enum constants into some kind of structure you can inspect at runtime. – Andy Dec 19 '18 at 01:59
  • @aaaaaa @matsko the double equality matrix doesn't really matter if you get into the habit of always using triple equals, with the sole useful exception that `null == x` is `true` if and only if `x` is `null` or `undefined`. And if you use a linter that flags sketchy(/accidental) use of double equals. – Andy Dec 19 '18 at 02:00
  • 2
    You say "+null == 0" as if that's a point to use triple =, @aaaaaa? Well... +null === 0, as well... – vbullinger Feb 08 '19 at 20:19

51 Answers51

1093

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.

LegionMammal978
  • 645
  • 9
  • 12
Artur Czajka
  • 16,393
  • 2
  • 25
  • 29
  • 6
    According to Wikipedia (http://en.wikipedia.org/wiki/JavaScript#Versions) it's applicable to Firefox 4, IE 9, Opera 11.60 and I know it works in Chrome. – Artur Czajka Mar 15 '12 at 11:05
  • 84
    This is the right answer now in 2012. More simple: `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` – Gabriel Llamas Apr 07 '12 at 10:29
  • 36
    For backward compatibility, `if (Object.freeze) { Object.freeze(DaysEnum); }` – saluce Aug 24 '12 at 15:56
  • 18
    I'd like to point out that doing `({ monday: {}, ` etc. means that if you convert that object to JSON via stringify you'll get `[{"day": {}}]` which isn't gonna work. – jcollum Feb 01 '13 at 00:20
  • @jcollum: Can you explain? Why wouldn't stringify be able to find the names 'monday', 'tuesday' etc? And where does that "day" string come from? Or is your comment referring to the square brackets ([ and ])? – Stijn de Witt Feb 01 '13 at 19:56
  • 2
    @StijndeWitt I'm pretty sure that stringify turns an object into "propname": "propvalue" -- in this case the prop value is an empty object (if the incoming object looks like {day: DaysEnum.monday}). – jcollum Feb 01 '13 at 22:47
  • @StijndeWitt i did end up using this enum scheme though, just used something to the effect of `{monday: "m", tuesday: "t"}` so that I could send the enum values over a wire. – jcollum Feb 01 '13 at 22:48
  • @jcollum: I would think that would be weird. What does stringify do when you send it an object with attributes that are not strings or numbers but arrays or objects? It would have to encode them like "name": [] or "name": {} right? I am not so convinced it really is a problem until I see an example of it :) – Stijn de Witt Feb 04 '13 at 14:52
  • 2
    @StijndeWitt http://jsfiddle.net/jcollum/eesAN/4/ you'll have a hard time turning foxtrot back into alpha.a after the stringify – jcollum Feb 04 '13 at 17:24
  • 1
    @jcollum: Ok I get your example now. So you are saying that 'enums' whose values are objects (whether frozen or not) cannot be serialized back and forth using stringify because afterwards they will fail the identity check right? So if you need that you either have to use numbers or other 'value-objects' or implement a custom stringify... But very good point indeed, I never thought of that before. – Stijn de Witt Feb 19 '13 at 14:13
  • @saluce wouldn't that mean that IE could have bugs the rest of the browser's wouldn't have though? Seems dangerous to use this since it won't work prior to IE 9 so you'd have a good case for IE specific bugs :(, sad looking at the compatibility, FF 4, Chrome 6, IE 9 :O – John Feb 27 '13 at 22:10
  • @John You could define Object.freeze as no-op pass-trough if it doesn't exist. It won't solve the problem, but will make the solution somewhat forward-compatible. – Artur Czajka Mar 04 '13 at 06:00
  • If you did want to use the number, you can get it with `DaysEnum.monday.toString()`. – Dan Mar 19 '13 at 19:32
  • 2
    As far as I'm concerned, this is THE correct answer. It is simple and easy to read. Most importantly, though, it prevents others from screwing with the enum's values, which is an essential property of enums in languages that natively support them. – Sildoreth Jan 16 '14 at 19:23
  • @GabrielLlamas Sometimes, though, you do need the ordinal of an enumerated constant. – Ben Leggiero Apr 09 '14 at 05:35
  • 13
    @Supuhstar My opinion about this question now is different. Don't use freeze(), it's completely useless and a waste of time doing "stupid" things. If you want to expose an enum, simply expose this: `var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}`. Comparing objects like in my previous comment is MUCH MORE SLOWER than comparing numbers. – Gabriel Llamas Apr 09 '14 at 07:58
  • 2
    @Supuhstar - Using the ordinal is outside the purpose of the enum structure. Enums should strictly be used for equality. Setting the values to empty objects may be slower, but it prevents abusive uses of the enum - e.g. with ordinals, a developer would be tempted to use the enum for sorting purposes. Another structure is better suited. – aaaaaa Mar 23 '16 at 19:28
  • 5
    The problem I noticed when doing: ({ monday: {}, tuesday: {} etc. was that in testing, you don't get very helpful messages when it fails. For example: AssertionError: expected {} to equal {} – Julian Mann Aug 20 '16 at 23:09
  • @CasBloem `const` will still allow you to change the values of the object. – George Apr 12 '18 at 13:21
  • 1
    @GabrielLlamas comparing objects with `===` is quite fast. In theory, it should be about the same as comparing two numbers, because it boils down to checking if two pointer values (integers) are equal. – Andy Jul 25 '18 at 00:15
  • @Andy It is double the complexity of comparing two numbers (sometimes triple) because the browser also has to check the type. Sure, sure, the browser can try to guesstimate the type and usually be right. But, noone can know for certain because we have yet to get rid of the evil eval method (`var innocentInt = 0, bigObject = {}; eval("innocentInt = bigObject");`) – Jack Giffin Jul 26 '18 at 00:29
  • Doesn't the browser have to check the type of any variables being compared, whether they are numbers, objects, strings, etc? – Andy Jul 27 '18 at 01:26
  • 1
    @Andy No. It does not. For example, `function sumItUp(x){x|=0; var sum = 0; while (x > 0) { sum = (sum + x) | 0; x--; } return sum; }`. In this function, there is no possible way for x or sum to escape being integers. Thus, it is safe for the browser to remove type checking from the operations involving x and sum, instead directly streaming the Javascript straight into lickety-fast assembly that uses statically typed integers to perform the summation. Obviously, IE (or any piece of crap made my microsoft for that matter) is too barbaric and archaic for these optimizations, but Chrome and FF do – Jack Giffin Nov 28 '18 at 00:34
  • 2
    **ES6 update: Use JavaScript Proxies to to create an Enum class.** Throw compile-time errors when trying to access enumerators that don't exist or adding/updating enumerators--just like traditional Enums. 15 lines of code.https://stackoverflow.com/a/49309248/2757916 – Govind Rai Dec 11 '18 at 23:10
  • 3
    @GovindRai Please : proxies only throw an exception when accessing them (there are "-" ), are horribly -, and severely . For a solution that solves all of these problems, see https://stackoverflow.com/a/50355530/5601591 – Jack Giffin Jul 10 '19 at 18:06
  • What exactly does freeze do? – Aaron Franke Dec 06 '19 at 04:23
  • @AaronFranke See the linked source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze – Artur Czajka Dec 09 '19 at 00:28
  • 1
    @AaronFranke ** ** when used this way! Looking up enumerations on objects is slow, even with JIT. For the maximum performance, what you want is `var b=2` in the minified output, not `var b=a.tuesday` (where `a` is DaysEnum and `b` is day). However, typing numbers into the source code makes it hard/impossible to read, maintain, and hand off. Closure Compiler with ADVANCED_OPTIMIZATIONS can inline the variables as numbers. However, it cannot inline when Object.freeze is used. See https://stackoverflow.com/a/50355530/5601591 for more. – Jack Giffin Feb 22 '20 at 18:22
  • @JackGiffin the performance part of your answer isn't as clear. There's a long-updated answer to another question, which shows, there is little-to-no difference in performance: https://stackoverflow.com/a/23198265/572370. I'd say it all depends on the particular use case and **everyone should check their own performance**. – Artur Czajka Feb 24 '20 at 13:30
  • 1
    @ArturCzajka My objection is not that frozen objects are ill-performant, rather that Object.freeze hinders the performance of the minified production code. Visit [this page](https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540compilation_level%2520ADVANCED_OPTIMIZATIONS%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250Aconst%2520DaysEnum%2520%253D%2520Object.freeze(%257Bmonday%253A1%252C%2520tuesday%253A2%257D)%250Aalert(DaysEnum.monday)%253B). With freeze, it's `alert(a.a)`. Without, it's `alert(1)`. – Jack Giffin Feb 25 '20 at 10:40
621

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.

Gareth
  • 115,773
  • 31
  • 143
  • 154
  • 386
    This was stated in another answer, but since this answer is the accepted answer, I'll post this here. The OP's solution is correct. It will be even better, though, if used with `Object.freeze()`. This will prevent other code from changing the enum's values. Example: `var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2});` – Sildoreth Jan 16 '14 at 19:29
  • 5
    @TolgaE thank you for that library! It inspired me to not only boil it down to the bare minimum, but also add a couple features! I've forked yours and put it all here: https://github.com/BlueHuskyStudios/Micro-JS-Enum – Ben Leggiero Apr 09 '14 at 05:26
  • 3
    @Supuhstar That's great! I'm glad you could use it.. Feel free to make a pull request if you wanted it merged in this library, then I can update the npm library – Tolga E Apr 09 '14 at 15:00
  • 2
    If anyone is interested, I have [implemented](https://github.com/vivin/enumjs) type-safe enums similar to how they are in Java. This means you can do `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. – Vivin Paliath Sep 18 '15 at 22:16
  • 3
    I'm not sure I agree with the "meaningful string" suggestion. Enums should not be thought of as strings or numbers; they are abstract data types. It should not be possible to "output the current value" without some helper method. In Java and .NET, its the `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. – Rabadash8820 Jul 06 '17 at 18:51
  • **ES6 update: Use JavaScript Proxies to mock Enums.** Throw compile-time errors when trying to access enumerators that don't exist--just like traditional Enums. 10 lines of code. stackoverflow.com/a/49309248/2757916 – Govind Rai Dec 11 '18 at 23:12
  • 1
    @GovindRai Please : proxies only throw an exception when accessing them (there are "-" ), are horribly -, and severely . For a solution that solves all of these problems, see https://stackoverflow.com/a/50355530/5601591 – Jack Giffin Jul 10 '19 at 18:08
516

UPDATE

Thanks for all the upvotes everyone, but 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

informatik01
  • 15,174
  • 9
  • 67
  • 100
Stijn de Witt
  • 31,992
  • 6
  • 67
  • 75
  • so then how would you go and create simply a SIZE if you only have its name? – Johanisma Nov 10 '11 at 04:06
  • 2
    @Johanisma: That use case does not realy make sense for enums as the whole idea of them is that you know all values in advance. However there is nothing stopping you from adding extra values later in Javascript. I will add an example of that to my answer. – Stijn de Witt Nov 29 '11 at 10:43
  • 2
    +1 for the link to your post with the properties approach. Elegant in that the basic declarations are simple, as in the OP, with added properties feature when desired. – goodeye Apr 29 '14 at 15:35
  • @Stijin, really liked your updated solution. Posted code in comments on your blog and as a comment below. Basically, using a function, perform the properties build from an existing hash list and optionally freeze it (mkenum_2 in my list). Cheers. – Andrew Philips Sep 05 '14 at 19:10
  • There is also a library that implements it , also including nice features as to comparison and reversed search : https://github.com/adrai/enum – Roman M Sep 27 '14 at 15:35
  • In your blog post you mention serialization as one of the reasons why you no longer like this way of doing enums, but this problem could be solved by adding a toJSON() method. Yeah, it's another step to creating an enum, but you could probably do it as a mixin. – Kevin Wiskia Aug 09 '16 at 14:55
  • @KevinWiskia Yes, a `toJSON` method will get you halfway there. But you also need a `reviver` for deserialization... And that reviver has to be passed to `JSON.parse`... So you can't just add a `fromJSON` or something and be done with it. You need to guard that that reviver function is used whenever JSON might contain your fancy enum... So it does make stuff a lot more complicated. – Stijn de Witt Aug 10 '16 at 21:31
89

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.

Randolpho
  • 52,575
  • 15
  • 139
  • 173
  • 106
    there is type safety in javascript ? – Scott Evernden Aug 21 '09 at 21:02
  • 3
    So don't map values to object properties. Use getter to access enumerant (stored as a property of, say, "private" object). A naive implementation would look like - `var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1` – kangax Aug 22 '09 at 03:20
  • 3
    @Scott Evernden: point taken. @kangax: the point is that it's still a hack. Enums simply don't exist in Javascript, period, end of story. Even the pattern suggested by Tim Sylvester is still a less than ideal hack. – Randolpho Aug 22 '09 at 20:14
  • 2
    Sprinkling the code with literals is not very maintainable so it makes sense to create constants for it. Of course Javascript doesn't have constants either. So basically this is just a way to write clean code. It can't be enforced, but not much in Javascript can. You can re-define constants, or functions, or mostly anything. EG: document.getElementById = function() {alert("You are screwed. Javascript is not typesafe.");}; – Stijn de Witt Nov 29 '11 at 11:04
  • 3
    @Randolpho: What about Artur Czajka's Object.freeze? Wouldn't that work to prevent you from setting monday to thursday? – Michael Jan 05 '12 at 15:58
  • In practice, I have never worried about someone assigning to an enum constant...it's right up there with worrying that someone could redefine `undefined`. The kind of errors that are worth fretting over are invalid values getting assigned to a variable that is intended to be an enum type. `Object.freeze` can't help with that. – Andy Jul 25 '18 at 00:07
59

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');
Andre 'Fi'
  • 599
  • 4
  • 3
  • Love the simplicity of this answer! – Marquizzo Jun 19 '19 at 22:00
  • @Marquizzo (and OP) I created an improved version based on this answer: https://stackoverflow.com/a/60309416/1599699 – Andrew Feb 19 '20 at 21:26
  • @Andrew I created a separate and much more well-thought-out, carefully considered, and thoroughly vetted answer that I have used in production many times: https://stackoverflow.com/a/50355530/5601591 – Jack Giffin Jul 22 '20 at 23:43
57

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")});

Plunker demo

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
YakovL
  • 5,213
  • 10
  • 46
  • 71
Vitalii Fedorenko
  • 97,155
  • 26
  • 144
  • 111
  • This is the correct answer in theory. In practice, 2015 browser support is far from sufficient. Not production ready by far. – vbraun Jun 16 '15 at 08:14
  • 1
    Though browser support isn't there yet, this is the best answer since this is close to what `Symbol` is intended for. – rvighne Jul 12 '16 at 22:49
  • 6
    Meh...enum values often need to be serializable though, and Symbols aren't so handy to serialize and deserialize. – Andy Jul 25 '18 at 00:09
  • 6
    Is it just me or is `Object.freeze` only for people who haven't accepted the fact that "monkeypatch at your own risk" is the social contract of JS? – Andy Dec 19 '18 at 02:10
  • @Andy yes serialization is annoying. I ended up doing an explicit `toJSON` on the containing class to use this approach: https://stackoverflow.com/questions/58499828/how-to-convert-a-javascript-symbol-to-a-string-without-the-symbol-prefix – Ciro Santilli新疆棉花TRUMP BAN BAD Oct 22 '19 at 08:33
38

-

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.


Underscore-Notation Variables

As demonstrated in the chart above and example below, here are five easy steps to get started:

  1. 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.
  2. 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_.
  3. 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
  4. 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.
  5. 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<ENUMLEN_PET; i=i+1|0)
  petsFrequency[i] = 0;

for (var i=0, len=favoritePets.length|0, petId=0; i<len; i=i+1|0)
  petsFrequency[petId = favoritePets[i]|0] = (petsFrequency[petId]|0) + 1|0;

console.log({
    "cat": petsFrequency[ENUM_PET_CAT],
    "dog": petsFrequency[ENUM_PET_DOG],
    "rat": petsFrequency[ENUM_PET_RAT]
});

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 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.


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 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.

Addition extension diagram

(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.

Wringing code via Closure Compiler

'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!))
Minified Without Using Enumerations (length: 843 bytes (238 bytes longer than enumerated code))

Chart of code sizes



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.

// 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
}

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.

/// Hashmaps are slow, even with JIT juice
var ref = {};
ref.count = 10;
ref.value = "foobar";

Compare the code above to the code below.

/// 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";

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.

/// Hashmaps are slow, even with JIT juice
var a={count:10,value:"foobar"};

The minified code without enumerations is above and the minified code with enumerations is below.

/// Arrays, however, are always lightning fast
var a=[10,"foobar"];

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 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.

CodeMirror highlighting demonstration

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 ENUM_COLORENUM_DNE does not exist.


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.

Jack Giffin
  • 3,108
  • 2
  • 26
  • 45
  • Eh. I strongly prefer readability and ease of use and understanding to code size. – Andrew Feb 19 '20 at 20:17
  • 1
    @Andrew With my answer, you can have both. My answer results in the easiest to use/manage code and in the smallest minified code size. – Jack Giffin Feb 19 '20 at 23:02
  • 1
    My answer (new) is wayyy easier to use and manage. (To save time: https://stackoverflow.com/a/60309416/1599699) – Andrew Feb 20 '20 at 16:54
  • 2
    @Andrew I have attempted to apply your *Yet Another Enum (YEA!)* to the color parser example in my answer. However, I have found several problems that you may want to address. *YEA* has no way to extend enumerations with subclasses, forcing me to create separate parent and child classes, which could be quite hard to manage on large projects. *YEA* does not ensure the entry exists (e.x. `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. … – Jack Giffin Feb 22 '20 at 16:56
  • 2
    @Andrew … YEA hinders Closure Compiler's ability to minify. Compare the source code with YEA (3549 bytes) to the minified code with YEA (1344 bytes) to the minified code with my solution (604 bytes). Finally, YEA involves "mapping by name" because it separates string names from enumerated IDs. Mine only considers ID, so no "mapping by name" is needed, leading to simpler design and better performance. Thank you for sharing your solution, but it needs many fixes before it can be practical. – Jack Giffin Feb 22 '20 at 16:56
  • 1
    Extending enumerations with subclasses is definitely an advanced feature... One can implement that themselves. An enum yielding undefined for a non-existent entry is correct behavior... If you actually use a number as your enumeration key, that's on you... I've already established code size is not of high concern. Your solution literally requires people to type these obtuse enum keys like `ENUM_QT_BLAHBLAH_BLAHBLAHBLAH`, no one wants to use that. Mine keeps the code readable which is huge in actual JS programming. You don't know crap about practicality. – Andrew Feb 24 '20 at 01:51
  • 1
    My mistake though for sharing my solution with you, that's on me. I should have known better. – Andrew Feb 24 '20 at 01:52
  • 1
    @Andrew You are entitled to your opinion as I am to mine – Jack Giffin Feb 24 '20 at 22:13
  • 1
    excessive post formatting and code as images. Would recommend an edit. – java-addict301 Oct 13 '20 at 18:53
30

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.

j2k
  • 5
  • 1
  • 4
Govind Rai
  • 10,062
  • 7
  • 54
  • 74
  • How to do something like myEnum.valueOf("someStringValue")? Expected: in case the input string has a value of an element of the enumerator, should return the item. In case no item has that string value, throw exception. – sscarduzio Jan 31 '19 at 17:51
  • @sscarduzio you can override the default `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? – Govind Rai Jan 31 '19 at 18:00
  • My enum is const logLevelEnum = new Enum({ INFO: "info", DEBUG: "debug"}) and I parse from input an arbitrary string "info" or "debug". So I need something like currentLogLevel = logLevelEnum.parseOrThrow(settings.get("log_level")) – sscarduzio Feb 01 '19 at 13:33
  • 1
    Why couldn't you just do `logLevelEnum[settings.get("log_level")]`? adding `parseOrThrow` would just be repetitive to what the proxy traps are already doing for you. – Govind Rai Feb 01 '19 at 22:26
23

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
Duncan
  • 1,484
  • 15
  • 20
  • 3
    If you add `return this;` at the end of Enum you could do : `var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW');` – HBP Aug 21 '13 at 17:15
  • I didn't consider that, as it's not my normal method of doing things. But you're absolutely correct! I'll edit that in. – Duncan Aug 21 '13 at 17:20
  • I really like this although I'm not a big fan of mucking up Object space (with global function ENUM). Converted this to a mkenum function and added optional numeric assignments => var mixedUp = mkenum('BLACK', {RED: 0x0F00, BLUE: 0X0F, GREEN: 0x0F0, WHITE: 0x0FFF, ONE: 1}, TWO, THREE, FOUR); // Adding my code as an answer below. Thanks. – Andrew Philips Sep 05 '14 at 18:44
  • To be honest, I don't even use this anymore. I've been using Google's Closure Compiler, and this doesn't work too well (or it just complicates things) if you use the Advanced setting. So I've just gone back to standard object notation. – Duncan Sep 08 '14 at 17:32
  • 1
    `false` is the default for `writable`, `enumerable` and `configurable`. No need for chewing over defaults. – ceving Nov 02 '15 at 16:03
17

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.

Rob Hardy
  • 1,735
  • 13
  • 15
17

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 {/*....*/}
Abdennour TOUMI
  • 64,884
  • 28
  • 201
  • 207
  • 4
    Could you explain why having a parent class is an advantage, please? I feel like I'm missing something! – Jon G Feb 01 '17 at 09:57
  • 7
    Don't do that. `new ColorEnum()` makes absolutely no sense. – Bergi Jun 09 '17 at 01:43
  • 3
    extending an enum sounds crazy, really – Codii Jul 06 '17 at 13:30
  • once the language doesnt support it natively would make sense keep this convention and use like this! i agree! – xpto Oct 06 '17 at 07:31
  • 1
    I think (?) what OP is getting at, is: The benefit of pure static is that it's available everywhere as a singleton, and you don't _need_ to instantiate the class - OP's not suggesting that you do! I think what he's saying is that the superclass `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`. – Engineer Feb 11 '20 at 16:53
15

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
Chris
  • 175
  • 1
  • 3
15

Create an object literal:

const Modes = {
  DRAGGING: 'drag',
  SCALING:  'scale',
  CLICKED:  'click'
};
hvdd
  • 474
  • 4
  • 6
  • 12
    `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()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) alongside `const`. – rvighne Jul 12 '16 at 22:47
  • Please do not use `Object.freeze`. It prevent Closure Compiler from inlining the object. – Jack Giffin Oct 07 '18 at 13:21
11

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()
Yaroslav
  • 3,897
  • 4
  • 21
  • 32
8

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
Xeltor
  • 4,367
  • 3
  • 22
  • 25
  • 1
    @JackGiffin I agree that your answer is more performant and that mine may take more memory, although you shouldn't assume everyone wants an enum the way C++ implemented it. Please respect other answers and the developers that might prefer this one over yours. – Xeltor Dec 14 '18 at 15:09
7

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
David Miró
  • 2,069
  • 17
  • 18
7

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
    }

})();
Vivin Paliath
  • 87,975
  • 37
  • 202
  • 284
  • It looks nice, maybe you should check for existence of the `freeze` method for backward compatibility? E.g., `if (Object.freeze) { Object.freeze(values); }` – FBB Sep 23 '15 at 11:25
7

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
Andrew
  • 3,330
  • 1
  • 33
  • 53
6

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
}
Manohar Reddy Poreddy
  • 16,412
  • 7
  • 111
  • 98
5
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.

Shivanshu Goyal
  • 1,050
  • 1
  • 12
  • 20
  • This answer is underrated. It's one of my favorite ideas for its simplicity. In practice, I think I'll stick to strings because it's easier to debug for now. – Domino Dec 08 '15 at 21:16
  • Hmm, just make sure that this code doesn't get called twice... – Andrew Feb 19 '20 at 20:22
5

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
Ilya Gazman
  • 27,805
  • 19
  • 119
  • 190
5

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(', ') }

}
Joseph Merdrignac
  • 2,212
  • 2
  • 16
  • 16
5

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).

Carl Smith
  • 2,847
  • 21
  • 34
Aral Roca
  • 4,205
  • 4
  • 41
  • 67
  • 1
    @Carl Smith I might have missed some comments, but that's a quite substantial edit?! – Bergi Aug 24 '20 at 00:26
  • 2
    @Bergi, you're right, but it *is* the same answer still. I really just made the code for the generator cleaner, and added an explanation, but you're right, it's quite a big diff. – Carl Smith Aug 24 '20 at 00:30
4

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"
user2254487
  • 171
  • 2
  • 4
4

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
Blake Bowen
  • 989
  • 8
  • 30
4

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;
Gelin Luo
  • 13,399
  • 23
  • 77
  • 119
4

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>..."
Tim Kara
  • 93
  • 1
  • 5
Oooogi
  • 363
  • 4
  • 14
4

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

LNT
  • 788
  • 6
  • 15
4

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:

  1. no more objects of type CellState may be created
  2. you are guaranteed that no two enum instances are assigned the same code
  3. utility method to get the enum back from a string representation
  4. 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;
    
Marcus Junius Brutus
  • 23,022
  • 30
  • 155
  • 282
4

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
Julius
  • 9,779
  • 10
  • 29
  • 71
3

As of writing, October 2014 - so here is a contemporary solution. Am writing the solution as a Node Module, and have included a test using Mocha and Chai, as well as underscoreJS. You can easily ignore these, and just take the Enum code if preferred.

Seen a lot of posts with overly convoluted libraries etc. The solution to getting enum support in Javascript is so simple it really isn't needed. Here is the code:

File: enums.js

_ = require('underscore');

var _Enum = function () {

   var keys = _.map(arguments, function (value) {
      return value;
   });
   var self = {
      keys: keys
   };
   for (var i = 0; i < arguments.length; i++) {
      self[keys[i]] = i;
   }
   return self;
};

var fileFormatEnum = Object.freeze(_Enum('CSV', 'TSV'));
var encodingEnum = Object.freeze(_Enum('UTF8', 'SHIFT_JIS'));

exports.fileFormatEnum = fileFormatEnum;
exports.encodingEnum = encodingEnum;

And a test to illustrate what it gives you:

file: enumsSpec.js

var chai = require("chai"),
    assert = chai.assert,
    expect = chai.expect,
    should = chai.should(),
    enums = require('./enums'),
    _ = require('underscore');


describe('enums', function () {

    describe('fileFormatEnum', function () {
        it('should return expected fileFormat enum declarations', function () {
            var fileFormatEnum = enums.fileFormatEnum;
            should.exist(fileFormatEnum);
            assert('{"keys":["CSV","TSV"],"CSV":0,"TSV":1}' === JSON.stringify(fileFormatEnum), 'Unexpected format');
            assert('["CSV","TSV"]' === JSON.stringify(fileFormatEnum.keys), 'Unexpected keys format');
        });
    });

    describe('encodingEnum', function () {
        it('should return expected encoding enum declarations', function () {
            var encodingEnum = enums.encodingEnum;
            should.exist(encodingEnum);
            assert('{"keys":["UTF8","SHIFT_JIS"],"UTF8":0,"SHIFT_JIS":1}' === JSON.stringify(encodingEnum), 'Unexpected format');
            assert('["UTF8","SHIFT_JIS"]' === JSON.stringify(encodingEnum.keys), 'Unexpected keys format');
        });
    });

});

As you can see, you get an Enum factory, you can get all the keys simply by calling enum.keys, and you can match the keys themselves to integer constants. And you can reuse the factory with different values, and export those generated Enums using Node's modular approach.

Once again, if you are just a casual user, or in the browser etc, just take the factory part of the code, potentially removing underscore library too if you don't wish to use it in your code.

arcseldon
  • 29,753
  • 14
  • 104
  • 117
  • 5
    Could you post an answer with just the "here's how to do this as a casual user who just wants enums, not factories, underscores, or anything fancy"? – GreenAsJade Jan 02 '15 at 03:08
  • 5
    Even though this is pretty awesome from a developers eyepoint, it's not very clean or readable. The Enum solution from the OP is easier and more readable in every way, and therefore better to use. Still, pretty awsome that you came up with this. – David Jan 17 '15 at 19:55
3

It's easy to use, I think. https://stackoverflow.com/a/32245370/4365315

var A = {a:11, b:22}, 
enumA = new TypeHelper(A);

if(enumA.Value === A.b || enumA.Key === "a"){ 
... 
}

var keys = enumA.getAsList();//[object, object]

//set
enumA.setType(22, false);//setType(val, isKey)

enumA.setType("a", true);

enumA.setTypeByIndex(1);

UPDATE:

There is my helper codes(TypeHelper).

var Helper = {
    isEmpty: function (obj) {
        return !obj || obj === null || obj === undefined || Array.isArray(obj) && obj.length === 0;
    },

    isObject: function (obj) {
        return (typeof obj === 'object');
    },

    sortObjectKeys: function (object) {
        return Object.keys(object)
            .sort(function (a, b) {
                c = a - b;
                return c
            });
    },
    containsItem: function (arr, item) {
        if (arr && Array.isArray(arr)) {
            return arr.indexOf(item) > -1;
        } else {
            return arr === item;
        }
    },

    pushArray: function (arr1, arr2) {
        if (arr1 && arr2 && Array.isArray(arr1)) {
            arr1.push.apply(arr1, Array.isArray(arr2) ? arr2 : [arr2]);
        }
    }
};
function TypeHelper() {
    var _types = arguments[0],
        _defTypeIndex = 0,
        _currentType,
        _value,
        _allKeys = Helper.sortObjectKeys(_types);

    if (arguments.length == 2) {
        _defTypeIndex = arguments[1];
    }

    Object.defineProperties(this, {
        Key: {
            get: function () {
                return _currentType;
            },
            set: function (val) {
                _currentType.setType(val, true);
            },
            enumerable: true
        },
        Value: {
            get: function () {
                return _types[_currentType];
            },
            set: function (val) {
                _value.setType(val, false);
            },
            enumerable: true
        }
    });
    this.getAsList = function (keys) {
        var list = [];
        _allKeys.forEach(function (key, idx, array) {
            if (key && _types[key]) {

                if (!Helper.isEmpty(keys) && Helper.containsItem(keys, key) || Helper.isEmpty(keys)) {
                    var json = {};
                    json.Key = key;
                    json.Value = _types[key];
                    Helper.pushArray(list, json);
                }
            }
        });
        return list;
    };

    this.setType = function (value, isKey) {
        if (!Helper.isEmpty(value)) {
            Object.keys(_types).forEach(function (key, idx, array) {
                if (Helper.isObject(value)) {
                    if (value && value.Key == key) {
                        _currentType = key;
                    }
                } else if (isKey) {
                    if (value && value.toString() == key.toString()) {
                        _currentType = key;
                    }
                } else if (value && value.toString() == _types[key]) {
                    _currentType = key;
                }
            });
        } else {
            this.setDefaultType();
        }
        return isKey ? _types[_currentType] : _currentType;
    };

    this.setTypeByIndex = function (index) {
        for (var i = 0; i < _allKeys.length; i++) {
            if (index === i) {
                _currentType = _allKeys[index];
                break;
            }
        }
    };

    this.setDefaultType = function () {
        this.setTypeByIndex(_defTypeIndex);
    };

    this.setDefaultType();
}

var TypeA = {
    "-1": "Any",
    "2": "2L",
    "100": "100L",
    "200": "200L",
    "1000": "1000L"
};

var enumA = new TypeHelper(TypeA, 4);

document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");


enumA.setType("200L", false);
document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");

enumA.setDefaultType();
document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");


enumA.setTypeByIndex(1);
document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");

document.writeln("is equals = ", (enumA.Value == TypeA["2"]));
Community
  • 1
  • 1
Sherali Turdiyev
  • 1,586
  • 14
  • 26
3

I wrote enumerationjs a very tiny library to address the issue which ensures type safety, allow enum constants to inherit from a prototype, guaranties enum constants and enum types to be immutable + many little features. It allows to refactor a lot of code and move some logic inside the enum definition. Here is an example :

var CloseEventCodes = new Enumeration("closeEventCodes", {
  CLOSE_NORMAL:          { _id: 1000, info: "Connection closed normally" },
  CLOSE_GOING_AWAY:      { _id: 1001, info: "Connection closed going away" },
  CLOSE_PROTOCOL_ERROR:  { _id: 1002, info: "Connection closed due to protocol error"  },
  CLOSE_UNSUPPORTED:     { _id: 1003, info: "Connection closed due to unsupported operation" },
  CLOSE_NO_STATUS:       { _id: 1005, info: "Connection closed with no status" },
  CLOSE_ABNORMAL:        { _id: 1006, info: "Connection closed abnormally" },
  CLOSE_TOO_LARGE:       { _id: 1009, info: "Connection closed due to too large packet" }
},{ talk: function(){
    console.log(this.info); 
  }
});


CloseEventCodes.CLOSE_TOO_LARGE.talk(); //prints "Connection closed due to too large packet"
CloseEventCodes.CLOSE_TOO_LARGE instanceof CloseEventCodes //evaluates to true

Enumeration is basically a factory.

Fully documented guide available here. Hope this helps.

Jules Sam. Randolph
  • 1,828
  • 17
  • 31
  • That's not really an enumeration. But I can see this being very useful. –  Jul 14 '16 at 17:23
  • @Will, how would you call them^^ ? `AugmentedEnum` ? – Jules Sam. Randolph Jul 14 '16 at 18:07
  • 1
    I'm not sure... It's kind of a mix of enums + metadata (in your example). An enum just has a name and a struct value it can be cast to/from. You can put an unlimited number of properties on each using this. I've seen the pattern before, and the team that wrote it called the result constants... Included individual constant types such as DataType, which not only had a name, but had an ID value, valid operators (another group of constants!), valid transforms, etc. Really useful, but much more than just a simple enumeration. –  Jul 14 '16 at 18:13
  • Very ill performant. Makes all code slower. Makes minified code several times bigger. Very bad. – Jack Giffin Jun 23 '18 at 23:27
3
class Enum {
  constructor (...vals) {
    vals.forEach( val => {
      const CONSTANT = Symbol(val);
      Object.defineProperty(this, val.toUpperCase(), {
        get () {
          return CONSTANT;
        },
        set (val) {
          const enum_val = "CONSTANT";
          // generate TypeError associated with attempting to change the value of a constant
          enum_val = val;
        }
      });
    });
  }
}

Example of usage:

const COLORS = new Enum("red", "blue", "green");
papiro
  • 1,657
  • 15
  • 25
3

Update 05.11.2020:
Modified to include static fields and methods to closer replicate "true" enum behavior.

Has anyone tried doing this with a class that contains private fields and "get" accessors? I realize private class fields are still experimental at this point but it seems to work for the purposes of creating a class with immutable fields/properties. Browser support is decent as well. The only "major" browsers that don't support it are Firefox (which I'm sure they will soon) and IE (who cares).

DISCLAIMER:
I am not a developer. I was just looking for an answer to this question and started thinking about how I sometimes create "enhanced" enums in C# by creating classes with private fields and restricted property accessors.

Sample Class

class Sizes {
    // Private Fields
    static #_SMALL = 0;
    static #_MEDIUM = 1;
    static #_LARGE = 2;

    // Accessors for "get" functions only (no "set" functions)
    static get SMALL() { return this.#_SMALL; }
    static get MEDIUM() { return this.#_MEDIUM; }
    static get LARGE() { return this.#_LARGE; }
}

You should now be able to call your enums directly.

Sizes.SMALL; // 0
Sizes.MEDIUM; // 1
Sizes.LARGE; // 2

The combination of using private fields and limited accessors means that the enum values are well protected.

Sizes.SMALL = 10 // Sizes.SMALL is still 0
Sizes._SMALL = 10 // Sizes.SMALL is still 0
Sizes.#_SMALL = 10 // Sizes.SMALL is still 0
dsanchez
  • 808
  • 7
  • 8
  • This doesn't seem to provide more protection than what `Object.freeze()` does, and there is a lot of redundant code. – beruic Nov 23 '20 at 10:24
  • @beruic I would say it's more strongly typed and verbose. However, no two things in the code complete the same exact task so I would say it definitely isn't redundant. But as I've mentioned, I'm not a developer so what do I know. – dsanchez Dec 16 '20 at 01:32
2

I had done it a while ago using a mixture of __defineGetter__ and __defineSetter__ or defineProperty depending on the JS version.

Here's the enum generating function I made: https://gist.github.com/gfarrell/6716853

You'd use it like this:

var Colours = Enum('RED', 'GREEN', 'BLUE');

And it would create an immutable string:int dictionary (an enum).

GTF
  • 6,353
  • 4
  • 27
  • 55
  • Ok I rewrote it and now it is library agnostic (although it assumes some sort of AMD loader, but that can be removed). – GTF Sep 26 '13 at 17:03
2

You can try this:

   var Enum = Object.freeze({
            Role: Object.freeze({ Administrator: 1, Manager: 2, Supervisor: 3 }),
            Color:Object.freeze({RED : 0, GREEN : 1, BLUE : 2 })
            });

    alert(Enum.Role.Supervisor);
    alert(Enum.Color.GREEN);
    var currentColor=0;
    if(currentColor == Enum.Color.RED) {
       alert('Its Red');
    }
Muhammad Awais
  • 3,310
  • 1
  • 34
  • 32
1

Really like what @Duncan did above, but I don't like mucking up global Object function space with Enum, so I wrote the following:

function mkenum_1()
{
  var o = new Object();
  var c = -1;
  var f = function(e, v) { Object.defineProperty(o, e, { value:v, writable:false, enumerable:true, configurable:true })};

  for (i in arguments) {
    var e = arguments[i];
    if ((!!e) & (e.constructor == Object))
      for (j in e)
        f(j, (c=e[j]));
    else
      f(e, ++c);
    }

  return Object.freeze ? Object.freeze(o) : o;
}

var Sizes = mkenum_1('SMALL','MEDIUM',{LARGE: 100},'XLARGE');

console.log("MED := " + Sizes.MEDIUM);
console.log("LRG := " + Sizes.LARGE);

// Output is:
// MED := 1
// LRG := 100

@Stijin also has a neat solution (referring to his blog) which includes properties on these objects. I wrote some code for that, too, which I'm including next.

function mkenum_2(seed)
{
    var p = {};

    console.log("Seed := " + seed);

    for (k in seed) {
        var v = seed[k];

        if (v instanceof Array)
            p[(seed[k]=v[0])] = { value: v[0], name: v[1], code: v[2] };
        else
            p[v] = { value: v, name: k.toLowerCase(), code: k.substring(0,1) };
    }
    seed.properties = p;

    return Object.freeze ? Object.freeze(seed) : seed;
}

This version produces an additional property list allowing friendly name conversion and short codes. I like this version because one need not duplicate data entry in properties as the code does it for you.

var SizeEnum2 = mkenum_2({ SMALL: 1, MEDIUM: 2, LARGE: 3});
var SizeEnum3 = mkenum_2({ SMALL: [1, "small", "S"], MEDIUM: [2, "medium", "M"], LARGE: [3, "large", "L"] });

These two can be combined into a single processing unit, mkenum, (consume enums, assign values, create and add property list). However, as I've spent far too much time on this today already, I will leave the combination as an exercise for the dear reader.

Andrew Philips
  • 1,123
  • 12
  • 16
1

You can use Object.prototype.hasOwnProperty()

var findInEnum,
    colorEnum = {
    red : 0,
    green : 1,
    blue : 2
};

// later on

findInEnum = function (enumKey) {
  if (colorEnum.hasOwnProperty(enumKey)) {
    return enumKey+' Value: ' + colorEnum[enumKey]
  }
}

alert(findInEnum("blue"))
Pang
  • 8,605
  • 144
  • 77
  • 113
Gildas.Tambo
  • 20,147
  • 7
  • 44
  • 72
1

What is an enum in my opinion: It's an immutable object that is always accessible and you can compare items with eachother, but the items have common properties/methods, but the objects themselves or the values cannot be changed and they are instantiated only once.

Enums are imho used for comparing datatypes, settings, actions to take/reply things like that.

So for this you need objects with the same instance so you can check if it is a enum type if(something instanceof enum) Also if you get an enum object you want to be able to do stuff with it, regardless of the enum type, it should always respond in the same way.

In my case its comparing values of datatypes, but it could be anything, from modifying blocks in facing direction in a 3d game to passing values on to a specific object type registry.

Keeping in mind it is javascript and doesn't provide fixed enum type, you end up always making your own implementation and as this thread shows there are legions of implementations without one being the absoulte correct.


This is what I use for Enums. Since enums are immutable(or should be at least heh) I freeze the objects so they can't be manipulated easely.

The enums can be used by EnumField.STRING and they have their own methods that will work with their types. To test if something passed to an object you can use if(somevar instanceof EnumFieldSegment)

It may not be the most elegant solution and i'm open for improvements, but this type of immutable enum(unless you unfreeze it) is exactly the usecase I needed.

I also realise I could have overridden the prototype with a {} but my mind works better with this format ;-) shoot me.

/**
 * simple parameter object instantiator
 * @param name
 * @param value
 * @returns
 */
function p(name,value) {
    this.name = name;
    this.value = value;
    return Object.freeze(this);
}
/**
 * EnumFieldSegmentBase
 */
function EnumFieldSegmentBase() {
    this.fieldType = "STRING";
}
function dummyregex() {
}
dummyregex.prototype.test = function(str) {
    if(this.fieldType === "STRING") {
        maxlength = arguments[1];
        return str.length <= maxlength;
    }
    return true;
};

dummyregexposer = new dummyregex();
EnumFieldSegmentBase.prototype.getInputRegex = function() { 
    switch(this.fieldType) {
        case "STRING" :     return dummyregexposer;  
        case "INT":         return /^(\d+)?$/;
        case "DECIMAL2":    return /^\d+(\.\d{1,2}|\d+|\.)?$/;
        case "DECIMAL8":    return /^\d+(\.\d{1,8}|\d+|\.)?$/;
        // boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its  false, otherwise lets see what Boolean produces
        case "BOOLEAN":     return dummyregexposer;
    }
};
EnumFieldSegmentBase.prototype.convertToType = function($input) {
    var val = $input;
    switch(this.fieldType) {
        case "STRING" :         val = $input;break;
        case "INT":         val==""? val=0 :val = parseInt($input);break;
        case "DECIMAL2":    if($input === "" || $input === null) {$input = "0"}if($input.substr(-1) === "."){$input = $input+0};val = new Decimal2($input).toDP(2);break;
        case "DECIMAL8":    if($input === "" || $input === null) {$input = "0"}if($input.substr(-1) === "."){$input = $input+0};val = new Decimal8($input).toDP(8);break;
        // boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its  false, otherwise lets see what Boolean produces
        case "BOOLEAN":     val = (typeof $input == 'boolean' ? $input : (typeof $input === 'string' ? (($input === "false" || $input === "" || $input === "0") ? false : true) : new Boolean($input).valueOf()))  ;break;
    }
    return val;
};
EnumFieldSegmentBase.prototype.convertToString = function($input) {
    var val = $input;
    switch(this.fieldType) {
        case "STRING":      val = $input;break;
        case "INT":         val = $input+"";break;
        case "DECIMAL2":    val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+2 : $input.toString().indexOf('.')+2)) ;break;
        case "DECIMAL8":    val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+8 : $input.toString().indexOf('.')+8)) ;break;
        case "BOOLEAN":     val = $input ? "true" : "false"  ;break;
    }
    return val;
};
EnumFieldSegmentBase.prototype.compareValue = function($val1,$val2) {
    var val = false;
    switch(this.fieldType) {
        case "STRING":      val = ($val1===$val2);break;
        case "INT":         val = ($val1===$val2);break;
        case "DECIMAL2":    val = ($val1.comparedTo($val2)===0);break;
        case "DECIMAL8":    val = ($val1.comparedTo($val2)===0);break;
        case "BOOLEAN":     val = ($val1===$val2);break;
    }
    return val;
};

/**
 * EnumFieldSegment is an individual segment in the 
 * EnumField
 * @param $array An array consisting of object p
 */
function EnumFieldSegment() {
    for(c=0;c<arguments.length;c++) {
        if(arguments[c] instanceof p) {
            this[arguments[c].name] = arguments[c].value;
        }
    }
    return Object.freeze(this); 
}
EnumFieldSegment.prototype = new EnumFieldSegmentBase();
EnumFieldSegment.prototype.constructor = EnumFieldSegment;


/**
 * Simple enum to show what type of variable a Field type is.
 * @param STRING
 * @param INT
 * @param DECIMAL2
 * @param DECIMAL8
 * @param BOOLEAN
 * 
 */
EnumField = Object.freeze({STRING:      new EnumFieldSegment(new p("fieldType","STRING")), 
                            INT:        new EnumFieldSegment(new p("fieldType","INT")), 
                            DECIMAL2:   new EnumFieldSegment(new p("fieldType","DECIMAL2")), 
                            DECIMAL8:   new EnumFieldSegment(new p("fieldType","DECIMAL8")), 
                            BOOLEAN:    new EnumFieldSegment(new p("fieldType","BOOLEAN"))});
Tschallacka
  • 24,188
  • 10
  • 79
  • 121
1

The Alien solution is to make things as simple as possible:

  1. use enum keyword (reserved in javascript)
  2. If enum keyword is just reserved but not implemented in your javascript, define the following

    const enumerate = spec => spec.split(/\s*,\s*/)
      .reduce((e, n) => Object.assign(e,{[n]:n}), {}) 
    

Now, you can easily use it

const kwords = enumerate("begin,end, procedure,if")
console.log(kwords, kwords.if, kwords.if == "if", kwords.undef)

I see no reason to make the enum values explicit variables. The scripts are morphic anyway and it makes no difference if part of your code is a string or valid code. What really matters is that you do not need to deal with tons of quotation marks whenever use or define them.

Little Alien
  • 1
  • 5
  • 21
1

Read all the answers and didn't found any non-verbose and DRY solution. I use this one-liner:

const modes = ['DRAW', 'SCALE', 'DRAG'].reduce((o, v) => ({ ...o, [v]: v }), {});

it generates an object with human-readable values:

{
  DRAW: 'DRAW',
  SCALE: 'SCALE',
  DRAG: 'DRAG'
}
oluckyman
  • 2,850
  • 1
  • 24
  • 32
  • Fair solution, though you'd lose syntax hinting in a code editor – mattsven Apr 15 '19 at 22:16
  • Thanks! worked for me after converting to plain javascript. Anyone looking for plain javascript solution. `var mappingTypesValues = ["ONE_TO_ONE" ,"ONE_TO_MANY"].reduce(function(o,v){o[v]= v;return o} , {});` – jkb016 May 20 '19 at 19:52
  • 2
    I have added in fun colorful images to [my answer here](https://stackoverflow.com/a/50355530/5601591). Do you think that it might be a little less dry now? – Jack Giffin Jul 11 '19 at 17:00
1

There are basically two types of enums, global (like C) and object-like (like TypeScript). For global enums, do something like this-

// Note that // enum is optional, though it makes it look slightly better.
const // enum
  SUNDAY = 1,
  MONDAY = 2,
  TUESDAY = 3,
  WEDNSDAY = 4,
  THURSDAY = 5,
  FRIDAY = 6,
  SATURDAY = 7;

And for object-like enums, do this (like Artur Czajka's answer)-

// A trailing comma isn't required but is a good habit.
const Days = Object.freeze({
  SUNDAY = 1,
  MONDAY = 2,
  TUESDAY = 3,
  WEDSNDAY = 4,
  THURSDAY = 5,
  FRIDAY = 6,
  SATURDAY = 7,
});

or

const Days = {
  SUNDAY = 1,
  MONDAY = 2,
  TUESDAY = 3,
  WEDSNDAY = 4,
  THURSDAY = 5,
  FRIDAY = 6,
  SATURDAY = 7,
};
Object.freeze(Days);

The first way to declare object-like enums looks slightly cleaner. By the way, global and object-like enums aren't really correct terms, I made them up.

Edit:

A solution made (for global enums) by Aral Roca, it looks amazing but has a con of being slow (like 0.1 seconds slow)-

function* ENUM(count = 1) {
  while (true) yield count++;
}

and then

const [ RED, GREEN, BLUE ] = ENUM();
Novachief
  • 63
  • 6
0
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

EDIT: If you are going to serialize the object (to JSON for instance) you'll the id again.

Community
  • 1
  • 1
Pylinux
  • 9,386
  • 3
  • 52
  • 62
  • 1
    `{red:{green:{blue:{}}}}.unwindme()`! – Little Alien Nov 02 '16 at 21:30
  • Please try this: `if (JSON.parse(JSON.stringify(DaysEnum.monday)) != DaysEnum.monday) console.error('Oops!')`. Read my blog post [Enums in Javascript](https://stijndewitt.com/2014/01/26/enums-in-javascript/) which is about exactly this issue to find out why this happens. – Stijn de Witt Feb 01 '17 at 09:51
0

You can use a simple funcion to invert keys and values, it will work with arrays also as it converts numerical integer strings to numbers. The code is small, simple and reusable for this and other use cases.

var objInvert = function (obj) {
    var invert = {}
    for (var i in obj) {
      if (i.match(/^\d+$/)) i = parseInt(i,10)
      invert[obj[i]] = i
    }
    return invert
}
 
var musicStyles = Object.freeze(objInvert(['ROCK', 'SURF', 'METAL',
'BOSSA-NOVA','POP','INDIE']))

console.log(musicStyles)
David Lemon
  • 1,407
  • 10
  • 21
0

This answer is an alternative approach for specific circumstances. I needed a set of bitmask constants based on attribute sub-values (cases where an attribute value is an array or list of values). It encompasses the equivalent of several overlapping enums.

I created a class to both store and generate the bitmask values. I can then use the pseudo-constant bitmask values this way to test, for example, if green is present in an RGB value:

if (value & Ez.G) {...}

In my code I create only one instance of this class. There doesn't seem to be a clean way to do this without instantiating at least one instance of the class. Here is the class declaration and bitmask value generation code:

class Ez {
constructor() {
    let rgba = ["R", "G", "B", "A"];
    let rgbm = rgba.slice();
    rgbm.push("M");              // for feColorMatrix values attribute
    this.createValues(rgba);
    this.createValues(["H", "S", "L"]);
    this.createValues([rgba, rgbm]);
    this.createValues([attX, attY, attW, attH]);
}
createValues(a) {                // a for array
    let i, j;
    if (isA(a[0])) {             // max 2 dimensions
        let k = 1;
        for (i of a[0]) {
            for (j of a[1]) {
                this[i + j] = k;
                k *= 2;
            }
        }
    }
    else {                       // 1D array is simple loop
        for (i = 0, j = 1; i < a.length; i++, j *= 2)
            this[a[i]] = j;
   }
}

The 2D array is for the SVG feColorMatrix values attribute, which is a 4x5 matrix of RGBA by RGBAM, where M is a multiplier. The resulting Ez properties are Ez.RR, Ez.RG, etc.

jamess
  • 443
  • 4
  • 9
0
export const ButtonType = Object.freeze({ 
   DEFAULT: 'default', 
   BIG: 'big', 
   SMALL: 'small'
})

source: https://medium.com/@idanlevi2/enum-in-javascript-5f2ff500f149

Idan
  • 1,696
  • 15
  • 26
0

The com.recoyxgroup.javascript.enum package allows you to properly define enum and flags enum classes, where:

  • Every constant is represented as an immutable { _value: someNumber }
  • Every constant has a property attached to the enum class (E.CONSTANT_NAME)
  • Every constant has a friendly String (constantName)
  • Every constant has a Number (someNumber)
  • You can declare custom properties/methods using E.prototype.

Wherever a specific enum is expected, the convention is to do E(v), like this:

const { FlagsEnum } from 'com.recoyxgroup.javascript.enum';

const Rights = FlagsEnum('Rights', [
    'ADMINISTRATION',
    'REVIEW',
]);

function fn(rights) {
    rights = Rights(rights);
    console.log('administration' in rights, 'review' in rights);
}

fn( ['administration', 'review'] ); // true true
fn( 'administration' ); // true false
fn( undefined ); // false false

var r = Rights.ADMINISTRATION;
console.log( r == 'administration' );

As you can see, you can still compare the value to a String.

Definitions can be more specific:

const E = FlagsEnum('E', [
    ['Q', 0x10],
    ['K', 'someB'],
    ['L', [0x40, 'someL']],
]);

FlagsEnum products > Instance properties/methods

  • number (was going to be valueOf(), but because of JS ==, had to be 'number')
  • set()
  • exclude()
  • toggle()
  • filter()
  • valueOf()
  • toString()
Klaider
  • 3,140
  • 3
  • 20
  • 49
0

Here's my take on a (flagged) Enum factory. Here's a working demo.

/*
 * Notes: 
 * The proxy handler enables case insensitive property queries
 * BigInt is used to enable bitflag strings /w length > 52
*/
function EnumFactory() {
  const proxyfy = {
    construct(target, args) { 
      const caseInsensitiveHandler = { 
          get(target, key) {
          return target[key.toUpperCase()] || target[key];  
        } 
      };
      const proxified = new Proxy(new target(...args), caseInsensitiveHandler ); 
      return Object.freeze(proxified);
    },
  }
  const ProxiedEnumCtor = new Proxy(EnumCtor, proxyfy);
  const throwIf = (
      assertion = false, 
      message = `Unspecified error`, 
      ErrorType = Error ) => 
      assertion && (() => { throw new ErrorType(message); })();
  const hasFlag = (val, sub) => {
    throwIf(!val || !sub, "valueIn: missing parameters", RangeError);
    const andVal = (sub & val);
    return andVal !== BigInt(0) && andVal === val;
  };

  function EnumCtor(values) {
    throwIf(values.constructor !== Array || 
            values.length < 2 || 
        values.filter( v => v.constructor !== String ).length > 0,
      `EnumFactory: expected Array of at least 2 strings`, TypeError);
    const base = BigInt(1);
    this.NONE = BigInt(0);
    values.forEach( (v, i) => this[v.toUpperCase()] = base<<BigInt(i) );
  }

  EnumCtor.prototype = {
    get keys() { return Object.keys(this).slice(1); },
    subset(sub) {
      const arrayValues = this.keys;
      return new ProxiedEnumCtor(
        [...sub.toString(2)].reverse()
          .reduce( (acc, v, i) => ( +v < 1 ? acc : [...acc, arrayValues[i]] ), [] )
      );
    },
    getLabel(enumValue) {
      const tryLabel = Object.entries(this).find( value => value[1] === enumValue );
      return !enumValue || !tryLabel.length ? 
        "getLabel: no value parameter or value not in enum" :
        tryLabel.shift();
    },
    hasFlag(val, sub = this) { return hasFlag(val, sub); },
  };
  
  return arr => new ProxiedEnumCtor(arr);
}
KooiInc
  • 104,388
  • 28
  • 131
  • 164
0

Note: this answer is outdated. You should see my updated answer

I'm late, but in C/Java, enums are not a dictionary with numeric values, but classes. My attempt to emulate this:

function Enum(vals) {
  vals = vals.map((el) => ""+el);
  function T(value) {
    value = ""+value;
    if(vals.indexOf(value) === -1) {
         throw new Error("invalid enum value");
    }
    
    this.value = value;
  }
   

  T.prototype.toString = function() { return this.value; }
  T.values = function () { return vals; }

  for(var i = 0; i < vals.length; i++) {
    T[vals[i]] = new T(vals[i]);
  } 

  Object.freeze(T);

  return T;
 }

//Important: DO NOT use "new"!
var Colors = Enum(["RED","GREEN","BLUE"]);

//Changing a static property no effect...
Colors.RED = "something";

//...But you can still add to prototype
Colors.prototype.something = 100;
console.log(Colors.RED);
Nirvana
  • 63
  • 9
-1

You could try using https://bitbucket.org/snippets/frostbane/aAjxM.

my.namespace.ColorEnum = new Enum(
    "RED = 0",
    "GREEN",
    "BLUE"
)

It should work up to ie8.

mika
  • 309
  • 2
  • 9