Categories
javascript

How can I guarantee that my enums definition doesn’t change in JavaScript?

2351

Would the following make the objects fulfil all characteristics that enums have in JavaScript? Something like:

my.namespace.ColorEnum = {
  RED : 0,
  GREEN : 1,
  BLUE : 2
}

// later on

if(currentColor == my.namespace.ColorEnum.RED) {
  // whatever
}

Or is there some other way I can do this?

11

  • 179

    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, 2015 at 18:10

  • 210

    @matsko isn’t that just an argument against using ==?

    – sdm350

    Feb 24, 2015 at 21:40

  • 8

    0 == null returns false

    – mcont

    Apr 3, 2015 at 14:58

  • 14

    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, 2015 at 15:59


  • 75

    The double equality matrix is more confusing than microsoft word’s auto-formatting

    – aaaaaa

    Mar 23, 2016 at 20:32

1181

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.

26

  • 6

    According to Wikipedia (en.wikipedia.org/wiki/JavaScript#Versions) it’s applicable to Firefox 4, IE 9, Opera 11.60 and I know it works in Chrome.

    Mar 15, 2012 at 11:05


  • 85

    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

    Apr 7, 2012 at 10:29

  • 39

    For backward compatibility, if (Object.freeze) { Object.freeze(DaysEnum); }

    – saluce

    Aug 24, 2012 at 15:56


  • 19

    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 1, 2013 at 0:20


  • 15

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

    Apr 9, 2014 at 7:58


627

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.

7

  • 391

    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, 2014 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: github.com/BlueHuskyStudios/Micro-JS-Enum

    – Ky.

    Apr 9, 2014 at 5: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 9, 2014 at 15:00

  • 2

    If anyone is interested, I have implemented 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.

    Sep 18, 2015 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.

    Jul 6, 2017 at 18:51


522

UPDATE

I don’t think my answer below is the best way to write enums in JavaScript anymore. See my blog post for more details: Enums in JavaScript.


Alerting the name is already possible:

if (currentColor == my.namespace.ColorEnum.RED) {
   // alert name of currentColor (RED: 0)
   var col = my.namespace.ColorEnum;
   for (var name in col) {
     if (col[name] == col.RED)
       alert(name);
   }
}

Alternatively, you could make the values objects, so you can have the cake and eat it too:

var SIZE = {
  SMALL : {value: 0, name: "Small", code: "S"}, 
  MEDIUM: {value: 1, name: "Medium", code: "M"}, 
  LARGE : {value: 2, name: "Large", code: "L"}
};

var currentSize = SIZE.MEDIUM;
if (currentSize == SIZE.MEDIUM) {
  // this alerts: "1: Medium"
  alert(currentSize.value + ": " + currentSize.name);
}

In JavaScript, as it is a dynamic language, it is even possible to add enum values to the set later:

// Add EXTRALARGE size
SIZE.EXTRALARGE = {value: 3, name: "Extra Large", code: "XL"};

Remember, the fields of the enum (value, name and code in this example) are not needed for the identity check and are only there for convenience. Also the name of the size property itself does not need to be hard coded, but can also be set dynamically. So supposing you only know the name for your new enum value, you can still add it without problems:

// Add 'Extra Large' size, only knowing it's name
var name = "Extra Large";
SIZE[name] = {value: -1, name: name, code: "?"};

Of course this means that some assumptions can no longer be made (that value represents the correct order for the size for example).

Remember, in JavaScript an object is just like a map or hash table. A set of name-value pairs. You can loop through them or otherwise manipulate them without knowing much about them in advance.

Example

for (var sz in SIZE) {
  // sz will be the names of the objects in SIZE, so
  // 'SMALL', 'MEDIUM', 'LARGE', 'EXTRALARGE'
  var size = SIZE[sz]; // Get the object mapped to the name in sz
  for (var prop in size) {
    // Get all the properties of the size object, iterates over
    // 'value', 'name' and 'code'. You can inspect everything this way.        
  }
} 

And by the way, if you are interested in namespaces, you may want to have a look at my solution for simple but powerful namespace and dependency management for JavaScript: Packages JS

7

  • so then how would you go and create simply a SIZE if you only have its name?

    – Johanisma

    Nov 10, 2011 at 4: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.

    Nov 29, 2011 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, 2014 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.

    Sep 5, 2014 at 19:10

  • There is also a library that implements it , also including nice features as to comparison and reversed search : github.com/adrai/enum

    – Roman M

    Sep 27, 2014 at 15:35