JavaScript: Can a nested object value reference its parent object key? - javascript

I have an object as follows:
var obj = {
parentKey : {
nestedKey1 : value,
nestedKey2 : function () {
return parentKey + nestedKey2;
}
}
};
Is there a way to use the values within the actual name of the parentKey and nestedKey1 and/or nestedKey2 in a function held in one of the nested key-value pairs as the one above?
In my actual scenario the keys are all numerical values I wish to use as criteria for a for loop.
Updated Scenario To Question
As an exercise to learn JavaScript I decided to code the logic of an elevator system. To do this I have made an object which represents the building and this object contains each floor, nested within each floor are the data of the number of people wishing to travel to another floor as follows:
var floorData = {
<floor number> : {
<people going to floor X> : <number of people>,
<people going to floor Y> : <number of people>,
peopleGoingUp : <formula that sums amount of people going up>,
peopleGoingDown : <formula that sums amount of people going down>
}
};
I want to include within each <floor number> object a property that sums the amount of peopleGoingUp and peopleGoingDown by means of a formula. For this formula to work as I intend I need to access the <floor number> name which is a numeric value from within the peopleGoingUp and peopleGoingDown formulas.
Here is my working example thus far and I have included peopleGoingUp and peopleGoingDown formulas in floor theBuilding[2]. What I wish for is to change the hand entered value of theParentKey to equal the name of the parent object.
// Total number of floors in building global object
var totalFloors = 3;
// Building object including amount of people waiting to take lift to other floors on each level of the building
var theBuilding = {
0 : {
1 : 2,
2 : 0,
3 : 1,
},
1 : {
0 : 1,
2 : 3,
3 : 2,
},
2: {
0 : 2,
1 : 1,
3 : 4,
goingUp : function () {
var sum = 0;
var theParentKey = 2; // this is supposed to be a direct reference to the parent object name to this nested object and thus equal to 2
for (i = 0; i <= totalFloors; i++) { // loop for total floors
if (i !== theParentKey && i >= theParentKey) {//sum if i isn't = this floor number and that i is greater than this floor number
sum += this[i]
}
};
return sum;
},
goingDown : function () {
var sum = 0;
var theParentKey = 2; // this is supposed to be a direct reference to the parent object name to this nested object and thus equal to 2
for (i = 0; i <= totalFloors; i++) { // loop for total floors from lowest
if (i !== theParentKey && i <= theParentKey) { //sum if i isn't = this floor number and that i is less than this floor number
sum += this[i]
}
};
return sum;
},
3 : {
0 : 0,
1 : 1,
2 : 4
}
};
console.log(theBuilding[2].goingUp()); // 4
console.log(theBuilding[2].goingDown()); // 3

Is there a way to use the values within the actual name of the
parentKey and nestedKey1 and/or nestedKey2 in a function held in one
of the nested key-value pairs as the one above?
You can do it in two ways.
Using lexical scope
The function inside nestedKey2 has access to the global scope. You can therefore reach every property inside obj using .:
obj.Parentkey
Using this
This is a bit more tricky as this inside a function in a multilevel object will point to the "same level" of the object as the function resides in. You can use this together with . to reach the lower levels of the object but there is no way to reach the higher levels.
You can, however, use a workaround implementing a circular reference:
var myObj = {
levelAccess: function() {
this.two.__ = this;
return this;
},
one: 'one',
two:
{
__: [],
myFunction: function () {return this.__.one}
}
}.levelAccess();
This solution requires that you add a __ property for each level that needs access to a higher level and then initialises all the __ properties via the levelAccess function. The solution should not cause any memory leaks, see this.

If I understood your question, you want to get names of the keys. You can use Object.keys() while passing the current object this.
var obj = {
parentKey : {
nestedKey1: 3,
nestedKey2: function () {
return Object.keys(this);
}
}
};
console.log(obj.parentKey.nestedKey2()); // ['nestedKey1', 'nestedKey2']

See this is an example
var obj = {
parentKey : {
nestedKey1 : "value",
nestedKey2 : function () {
var c = obj.parentKey;
//console.log("we get "+ c.nestedKey1 +" & "+ c.nestedKey3);
//you can update values
//obj.parentKey.nestedKey1 = "new value";
console.log("we get "+ c.nestedKey1 +" & "+ c.nestedKey3);
return "we get "+ c.nestedKey1 +" & "+ c.nestedKey3;
},
nestedKey3 : "another value"
}
};
obj.parentKey.nestedKey2();
var an = window["obj"]["parentKey"]["nestedKey3"];
console.log(an);

Related

The key value of one object is changing on update of other similar type of object in javascript

I am using two different array object initialPermissionArr[item.id] and newPermissionArr[roleId] in two different functions
function(item){
vm.initialPermissionArr[item.id] = item.permissions.map(function (permission) {
permission.status = true;
return permission;
});
}
staticArr[item.id] = item.permissions.map(function (permission) {
permission.status = true;
return permission;
});
newpermissionArr[item.id] = vm.initialPermissionArr[item.id];
Below function updates the array, if same object is found it updates the status and if new object is found it pushes to newPermissionArr
function onChanageOfPermission(roleId,item) {
var flag = false ;
for (var key in newpermissionArr[roleId]){
if(newPermissionArr[roleId][key].id == item.id){
flag = true;
newPermissionArr[roleId][key].status = item.status;
break;
}
}
if (!flag) {
newPermissionArr[roleId].push(item);
}
}
So when newPermissionArr[roleId][key].status = item.status; is updated it also update the status in the initialPermissionArr[item.id] also.
And initial declaration is
var newPermissionArr = [];
var staticArr = [];
where for eg item is
{
roleId : 1,
permissions : [{"name": "A", "id" : 1},{ "name" : "B", "id" : 2 }]
}
I need initial object Array to remain same and at the end i need to compare the initial array with the modified array and need to find the reference however on updating the status it updates in both array. How to avoid this ?
The arrays reference the same object. To modify just one of them, you should use slice() function for clone the array:
newpermissionArr[item.id] = vm.initialPermissionArr[item.id].slice();
This is happening because of following line of code
newpermissionArr[item.id] = vm.initialPermissionArr[item.id];
Here object is passed by reference, so whenever newpermission is updated intialpermission will also be updated.
To fix this just copy the intialPermissionArr to newPermissionArr.
Unfortunately,plain javascript does not have any function like angular.copy. So you will have to do this in following way-
newrPermissionArr[item.id] = JSON.parse(JSON.stringify(vm.intialPermissionArr[item.id]));
this should fix your problem.
When you assign something to a and b and it is a pointer in memory to object c. Then as soon as you change it to c2 both a and b will get c2 from that point as they were just pointers to same location.

How to use javascript to loop through key , values and add up one key's value when the other's match

I have a dataset of records that look like this :
[{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":3
},
{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":1
}
]
The ct value changes in every record. If d1d, d1h, and d15m are the same in one or more records, I need to combine those records into one with the sum of all the ct values.
I do have jquery, can I use grep for this?
I realize the server side could do a better job of getting me this data , but I have zero control over that.
You don't have to use jQuery for this, vanilla JavaScript will do.
I'll show you two solutions to your problem;
Example 1: Abusing Array#reduce as an iterator
var intermediaryArray = [];
dataset.reduce(function(prev, curr) {
if(prev.d1d === curr.d1d && prev.d1h === curr.d1h && prev.d15m === curr.d15m) {
intermediaryArray.push({
d1d: prev.d1d,
d1h: prev.d1h,
d15m: prev.d15m,
ct: prev.ct + curr.ct
});
} else {
// push the one that wasn't the same
intermediaryArray.push(curr);
}
// return current element so reduce has something to work on
// for the next iteration.
return curr;
});
Example 2: Using Array#Map and Array#Reduce in conjunction
This example utilises underscore.js to demonstrate the logic behind what you want to do.
.map() produces the new array of grouped objects.
.groupBy() produces an array of subarrays containing the objects that pass the predicate that all objects must share the same d1d or grouping function.
.reduce() boils all subarrays down to one value, your object with both cts added to each other.
var merged = _.map(_.groupBy(a, 'd1d'), function(subGroup) {
return subGroup.reduce(function(prev, curr) {
return {
d1d: prev.d1d,
d1h: prev.d1h,
d15m: prev.d15m,
ct: prev.ct + curr.ct
};
});
});
Here's one possible solution:
var dataset = [{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":3
},
{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":1
}
]
function addCt(dataset) {
var ctMap = {}
var d1d, d1h, d15m, ct, key, value
for (var ii=0, record; record=dataset[ii]; ii++) {
key = record.d1d+"|"+record.d1h+"|"+record.d15m
value = ctMap[key]
if (!value) {
value = 0
}
value += record.ct
ctMap[key] = value
}
return ctMap
}
ctMap = addCt(dataset)
console.log(ctMap)
// { "2015-05-28T00:00:00.000Z|0|0": 4 }
You may want to construct the key in a different way. You may want set the value to an object containing the d1d, d1h, d15m and cumulated ct values, with a single object for all matching d1d, d1h and d15m values.

Is it possible to access a just specified property?

I'm using trying improve the readability of some code i'm writing. I pass an array of values to a function and want to be able to extract each. I'd like to check that the array is the expected size.
function useData(data) {
if (data.length != Data.Size) {
// ERROR
}
var name = data[Data.Indexes.NAME];
var age = data[Data.Indexes.AGE];
var weight = data[Data.Indexes.WEIGHT];
}
I want to know how many properties are in Data.Indexes, i've tried the following two methods,
var Data = {
Indexes : {
NAME : 0,
AGE : 1,
WEIGHT : 2
},
Size : Objects.keys(Indexes).length // Doesn't work.
};
Data.Size2 = Object.keys(Data.Indexes).length; // Works.
Can I find the size of Indexes whilst still inside Data?
You can make Size-property a function returning the number of properties in Indexes like so:
var Data = {
Indexes : {
NAME : 0,
AGE : 1,
WEIGHT : 2
},
Size : function() {return Object.keys(this.Indexes).length}
};
Now you can call it inside function useData():
function useData(data) {
if (data.length != Data.Size()) {/* ERROR */}
/* ... */
}
A second way makes Data.Size a getter-function. That way it's protected against overwriting:
var Data = {
Indexes : { /* ... */ },
get Size() {return Object.keys(this.Indexes).length
};
The getter is executed each time you refer to it:
function useData(data) {
if (data.length != Data.Size) {/* ERROR */}
/* ... */
}
Data.Size = 55; // nothing happens
Update: These ways doesn't protect Data.Indexes against changes. If you need that, have a look at Object.seal() and Object.freeze().

How can I call several variables that begin with the same letters and how can I call all variables that contain a certain value?

I need the following two codes
1) A code to select all variables that begin with "example"
2) A code to select all variables that have "true" as value for "available"
example1= {price:1000, size: 1000, available:true}
example2= {price:2000, size: 2000, available:false}
example3= {price:3000, size: 3000, available:true}
example4= {price:4000, size: 4000, available=true}
This is what I want to achieve with code one. As there are a lot of variables I need a quick way of doing it:
var allexampleprices=[example1.price, example2.price, example3.price, example4.price]
With the second code I want to get an array with all the names of the variables that contain the value "false"
Any help appreciated!
All of these are the exact same thing, assuming you're not in a function:
var myVar = 7;
window.myVar = 7;
window["myVar"] = 7;
Therefore, you can access any global variable (a variable defined outside a function) by using the window[ insertString ] method. If you wanted to search through every property on the window object to find one called example, you'd do:
for( var k in window ){
if(/example/.test(k)){
var myExample = window[k];
// Do stuff
}
}
I would HIGHLY recommend against this method, though, for many reasons. To start, it's a horribly bad practice to put anything in the global scope. Variables will start colliding all over the place on big projects. Also, the window object has soooooo many properties that searching through all of them is a horrible performance drain.
Having said all of that, I've devised an example of what you should do, including the helper functions to do it:
var objects =
{
example1:
{
price: 1000,
size: 1000,
available: true
},
example2:
{
price: 2000,
size: 2000,
available: false
},
example3:
{
price: 3000,
size: 3000,
available: true
},
example4:
{
price: 4000,
size: 4000,
available: true
}
}
function filter(obj, comparator){
var list = [];
for(var k in obj){
if(obj.hasOwnProperty(k)){ // fix for IE
if(comparator(obj[k], k, obj)) list.push(obj[k]);
}
}
return list;
}
function isExample(obj, key){
if(/^example/.test( key.toLowerCase() )) return true;
}
function isAvailable(obj){
if(obj.available) return true;
}
/**
* And here's how you use it
*/
var examples = filter(objects, isExample);
var available = filter(objects, isAvailable);
var availableExample = filter(examples, isAvailable);
The filter function returns an array of all of the matching objects.
--- EDIT ---
You want it to say the names of the objects in the console. I'm assuming what you mean is that the console currently shows [object, object, object, object]. There are two ways to do this:
(1) Put the name in the object itself
example1:
{
name: "example1",
price: 1000,
size: 1000,
available: true
}
(2) Capture the name in the filter operation
var names = [];
var examples = filter(objects, function(obj, name){
if(/^example/.test( name.toLowerCase() )){
names.push(name);
return true;
}
});
console.log(names);
I do like below if all variables are in global scope
var passedElements = [];
for(var i = 1, l = 100 /* Maximum number of variable */ ; i < l; i++){
if(window['example' + i]){
var temp = window['example' + i];
if(temp.available === true){
passedElements.push(temp);
}
}/*
else{
// Dont break the loop here, if any variable is missing in between
two variables it will fail. Eg : Example1, Example3.. 2 is missing.
}*/
}
console.log(passedElements);
I hope it will help.
It's seems like follwing line is generated by some logical code:
var example1= {price:1000, size: 1000, available:true}
Why dont you simply store the vaiable names in another array that should give you the solution of Q-1.
Then you can easily travers through all of the vaiables (array) to find the vairables that have "true" as value for "available"

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

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?
Since 1.8.5 it's possible to seal and freeze the object, so define the above as:
const DaysEnum = Object.freeze({"monday":1, "tuesday":2, "wednesday":3, ...})
or
const DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Object.freeze(DaysEnum)
and voila! JS enums.
However, this doesn't prevent you from assigning an undesired value to a variable, which is often the main goal of enums:
let day = DaysEnum.tuesday
day = 298832342 // goes through without any errors
One way to ensure a stronger degree of type safety (with enums or otherwise) is to use a tool like TypeScript or Flow.
Quotes aren't needed but I kept them for consistency.
This isn't much of an answer, but I'd say that works just fine, personally
Having said that, since it doesn't matter what the values are (you've used 0, 1, 2), I'd use a meaningful string in case you ever wanted to output the current value.
UPDATE
I don't think my answer below is the best way to write enums in JavaScript anymore. See my blog post for more details: Enums in JavaScript.
Alerting the name is already possible:
if (currentColor == my.namespace.ColorEnum.RED) {
// alert name of currentColor (RED: 0)
var col = my.namespace.ColorEnum;
for (var name in col) {
if (col[name] == col.RED)
alert(name);
}
}
Alternatively, you could make the values objects, so you can have the cake and eat it too:
var SIZE = {
SMALL : {value: 0, name: "Small", code: "S"},
MEDIUM: {value: 1, name: "Medium", code: "M"},
LARGE : {value: 2, name: "Large", code: "L"}
};
var currentSize = SIZE.MEDIUM;
if (currentSize == SIZE.MEDIUM) {
// this alerts: "1: Medium"
alert(currentSize.value + ": " + currentSize.name);
}
In JavaScript, as it is a dynamic language, it is even possible to add enum values to the set later:
// Add EXTRALARGE size
SIZE.EXTRALARGE = {value: 3, name: "Extra Large", code: "XL"};
Remember, the fields of the enum (value, name and code in this example) are not needed for the identity check and are only there for convenience. Also the name of the size property itself does not need to be hard coded, but can also be set dynamically. So supposing you only know the name for your new enum value, you can still add it without problems:
// Add 'Extra Large' size, only knowing it's name
var name = "Extra Large";
SIZE[name] = {value: -1, name: name, code: "?"};
Of course this means that some assumptions can no longer be made (that value represents the correct order for the size for example).
Remember, in JavaScript an object is just like a map or hash table. A set of name-value pairs. You can loop through them or otherwise manipulate them without knowing much about them in advance.
Example
for (var sz in SIZE) {
// sz will be the names of the objects in SIZE, so
// 'SMALL', 'MEDIUM', 'LARGE', 'EXTRALARGE'
var size = SIZE[sz]; // Get the object mapped to the name in sz
for (var prop in size) {
// Get all the properties of the size object, iterates over
// 'value', 'name' and 'code'. You can inspect everything this way.
}
}
And by the way, if you are interested in namespaces, you may want to have a look at my solution for simple but powerful namespace and dependency management for JavaScript: Packages JS
Bottom line: You can't.
You can fake it, but you won't get type safety. Typically this is done by creating a simple dictionary of string values mapped to integer values. For example:
var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Document.Write("Enumerant: " + DaysEnum.tuesday);
The problem with this approach? You can accidentally redefine your enumerant, or accidentally have duplicate enumerant values. For example:
DaysEnum.monday = 4; // whoops, monday is now thursday, too
Edit
What about Artur Czajka's Object.freeze? Wouldn't that work to prevent you from setting monday to thursday? โ€“ Fry Quad
Absolutely, Object.freeze would totally fix the problem I complained about. I would like to remind everyone that when I wrote the above, Object.freeze didn't really exist.
Now.... now it opens up some very interesting possibilities.
Edit 2
Here's a very good library for creating enums.
http://www.2ality.com/2011/10/enums.html
While it probably doesn't fit every valid use of enums, it goes a very long way.
Here's what we all want:
function Enum(constantsList) {
for (var i in constantsList) {
this[constantsList[i]] = i;
}
}
Now you can create your enums:
var YesNo = new Enum(['NO', 'YES']);
var Color = new Enum(['RED', 'GREEN', 'BLUE']);
By doing this, constants can be acessed in the usual way (YesNo.YES, Color.GREEN) and they get a sequential int value (NO = 0, YES = 1; RED = 0, GREEN = 1, BLUE = 2).
You can also add methods, by using Enum.prototype:
Enum.prototype.values = function() {
return this.allValues;
/* for the above to work, you'd need to do
this.allValues = constantsList at the constructor */
};
Edit - small improvement - now with varargs: (unfortunately it doesn't work properly on IE :S... should stick with previous version then)
function Enum() {
for (var i in arguments) {
this[arguments[i]] = i;
}
}
var YesNo = new Enum('NO', 'YES');
var Color = new Enum('RED', 'GREEN', 'BLUE');
In most modern browsers, there is a symbol primitive data type which can be used to create an enumeration. It will ensure type safety of the enum as each symbol value is guaranteed by JavaScript to be unique, i.e. Symbol() != Symbol(). For example:
const COLOR = Object.freeze({RED: Symbol(), BLUE: Symbol()});
To simplify debugging, you can add a description to enum values:
const COLOR = Object.freeze({RED: Symbol("RED"), BLUE: Symbol("BLUE")});
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
๐—ฆ๐—ฒ๐—น๐—ณ-๐——๐—ฒ๐˜€๐—ฐ๐—ฟ๐—ถ๐—ฝ๐˜๐—ถ๐˜ƒ๐—ฒ ๐—˜๐˜…๐˜๐—ฒ๐—ป๐˜€๐—ถ๐—ฏ๐—น๐—ฒ ๐—ฉ๐—ฎ๐—ฟ๐—ถ๐—ฎ๐—ฏ๐—น๐—ฒ ๐—ก๐—ฎ๐—บ๐—ฒ๐˜€
Let's cut straight to the problem: file size. Every other answer listed here bloats your minified code to the extreme. I present to you that for the best possible reduction in code size by minification, performance, readability of code, large scale project management, and syntax hinting in many code editors, this is the correct way to do enumerations: underscore-notation variables.
As demonstrated in the chart above and example below, here are five easy steps to get started:
Determine a name for the enumeration group. Think of a noun that can describe the purpose of the enumeration or at least the entries in the enumeration. For example, a group of enumerations representing colors choosable by the user might be better named COLORCHOICES than COLORS.
Decide whether enumerations in the group are mutually-exclusive or independent. If mutually-exclusive, start each enumerated variable name with ENUM_. If independent or side-by-side, use INDEX_.
For each entry, create a new local variable whose name starts with ENUM_ or INDEX_, then the name of the group, then an underscore, then a unique friendly name for the property
Add a ENUMLENGTH_, ENUMLEN_, INDEXLENGTH_, or INDEXLEN_ (whether LEN_ or LENGTH_ is personal preference) enumerated variable at the very end. You should use this variable wherever possible in your code to ensure that adding an extra entry to the enumeration and incrementing this value won't break your code.
Give each successive enumerated variable a value one more than the last, starting at 0. There are comments on this page that say 0 should not be used as an enumerated value because 0 == null, 0 == false, 0 == "", and other JS craziness. I submit to you that, to avoid this problem and boost performance at the same time, always use === and never let == appear in your code except with typeof (e.x. typeof X == "string"). In all my years of using ===, I have never once had a problem with using 0 as an enumeration value. If you are still squeamish, then 1 could be used as the starting value in ENUM_ enumerations (but not in INDEX_ enumerations) without performance penalty in many cases.
const ENUM_COLORENUM_RED = 0;
const ENUM_COLORENUM_GREEN = 1;
const ENUM_COLORENUM_BLUE = 2;
const ENUMLEN_COLORENUM = 3;
// later on
if(currentColor === ENUM_COLORENUM_RED) {
// whatever
}
Here is how I remember when to use INDEX_ and when to use ENUM_:
// Precondition: var arr = []; //
arr[INDEX_] = ENUM_;
However, ENUM_ can, in certain circumstances, be appropriate as an index such as when counting the occurrences of each item.
const ENUM_PET_CAT = 0,
ENUM_PET_DOG = 1,
ENUM_PET_RAT = 2,
ENUMLEN_PET = 3;
var favoritePets = [ENUM_PET_CAT, ENUM_PET_DOG, ENUM_PET_RAT,
ENUM_PET_DOG, ENUM_PET_DOG, ENUM_PET_CAT,
ENUM_PET_RAT, ENUM_PET_CAT, ENUM_PET_DOG];
var petsFrequency = [];
for (var i=0; i<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.
(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.
'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))
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.
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.
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.
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
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.
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 {/*....*/}
Create an object literal:
const Modes = {
DRAGGING: 'drag',
SCALING: 'scale',
CLICKED: 'click'
};
This is the solution that I use.
function Enum() {
this._enums = [];
this._lookups = {};
}
Enum.prototype.getEnums = function() {
return _enums;
}
Enum.prototype.forEach = function(callback){
var length = this._enums.length;
for (var i = 0; i < length; ++i){
callback(this._enums[i]);
}
}
Enum.prototype.addEnum = function(e) {
this._enums.push(e);
}
Enum.prototype.getByName = function(name) {
return this[name];
}
Enum.prototype.getByValue = function(field, value) {
var lookup = this._lookups[field];
if(lookup) {
return lookup[value];
} else {
this._lookups[field] = ( lookup = {});
var k = this._enums.length - 1;
for(; k >= 0; --k) {
var m = this._enums[k];
var j = m[field];
lookup[j] = m;
if(j == value) {
return m;
}
}
}
return null;
}
function defineEnum(definition) {
var k;
var e = new Enum();
for(k in definition) {
var j = definition[k];
e[k] = j;
e.addEnum(j)
}
return e;
}
And you define your enums like this:
var COLORS = defineEnum({
RED : {
value : 1,
string : 'red'
},
GREEN : {
value : 2,
string : 'green'
},
BLUE : {
value : 3,
string : 'blue'
}
});
And this is how you access your enums:
COLORS.BLUE.string
COLORS.BLUE.value
COLORS.getByName('BLUE').string
COLORS.getByValue('value', 1).string
COLORS.forEach(function(e){
// do what you want with e
});
I usually use the last 2 methods for mapping enums from message objects.
Some advantages to this approach:
Easy to declare enums
Easy to access your enums
Your enums can be complex types
The Enum class has some associative caching if you are using getByValue a lot
Some disadvantages:
Some messy memory management going on in there, as I keep the references to the enums
Still no type safety
If you're using Backbone, you can get full-blown enum functionality (find by id, name, custom members) for free using Backbone.Collection.
// enum instance members, optional
var Color = Backbone.Model.extend({
print : function() {
console.log("I am " + this.get("name"))
}
});
// enum creation
var Colors = new Backbone.Collection([
{ id : 1, name : "Red", rgb : 0xFF0000},
{ id : 2, name : "Green" , rgb : 0x00FF00},
{ id : 3, name : "Blue" , rgb : 0x0000FF}
], {
model : Color
});
// Expose members through public fields.
Colors.each(function(color) {
Colors[color.get("name")] = color;
});
// using
Colors.Red.print()
your answers are far too complicated
var buildSet = function(array) {
var set = {};
for (var i in array) {
var item = array[i];
set[item] = item;
}
return set;
}
var myEnum = buildSet(['RED','GREEN','BLUE']);
// myEnum.RED == 'RED' ...etc
I've modified the solution of Andre 'Fi':
function Enum() {
var that = this;
for (var i in arguments) {
that[arguments[i]] = i;
}
this.name = function(value) {
for (var key in that) {
if (that[key] == value) {
return key;
}
}
};
this.exist = function(value) {
return (typeof that.name(value) !== "undefined");
};
if (Object.freeze) {
Object.freeze(that);
}
}
Test:
var Color = new Enum('RED', 'GREEN', 'BLUE');
undefined
Color.name(Color.REDs)
undefined
Color.name(Color.RED)
"RED"
Color.exist(Color.REDs)
false
Color.exist(Color.RED)
true
I came up with this approach which is modeled after enums in Java. These are type-safe, and so you can perform instanceof checks as well.
You can define enums like this:
var Days = Enum.define("Days", ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]);
Days now refers to the Days enum:
Days.Monday instanceof Days; // true
Days.Friday.name(); // "Friday"
Days.Friday.ordinal(); // 4
Days.Sunday === Days.Sunday; // true
Days.Sunday === Days.Friday; // false
Days.Sunday.toString(); // "Sunday"
Days.toString() // "Days { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } "
Days.values().map(function(e) { return e.name(); }); //["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
Days.values()[4].name(); //"Friday"
Days.fromName("Thursday") === Days.Thursday // true
Days.fromName("Wednesday").name() // "Wednesday"
Days.Friday.fromName("Saturday").name() // "Saturday"
The implementation:
var Enum = (function () {
/**
* Function to define an enum
* #param typeName - The name of the enum.
* #param constants - The constants on the enum. Can be an array of strings, or an object where each key is an enum
* constant, and the values are objects that describe attributes that can be attached to the associated constant.
*/
function define(typeName, constants) {
/** Check Arguments **/
if (typeof typeName === "undefined") {
throw new TypeError("A name is required.");
}
if (!(constants instanceof Array) && (Object.getPrototypeOf(constants) !== Object.prototype)) {
throw new TypeError("The constants parameter must either be an array or an object.");
} else if ((constants instanceof Array) && constants.length === 0) {
throw new TypeError("Need to provide at least one constant.");
} else if ((constants instanceof Array) && !constants.reduce(function (isString, element) {
return isString && (typeof element === "string");
}, true)) {
throw new TypeError("One or more elements in the constant array is not a string.");
} else if (Object.getPrototypeOf(constants) === Object.prototype && !Object.keys(constants).reduce(function (isObject, constant) {
return Object.getPrototypeOf(constants[constant]) === Object.prototype;
}, true)) {
throw new TypeError("One or more constants do not have an associated object-value.");
}
var isArray = (constants instanceof Array);
var isObject = !isArray;
/** Private sentinel-object used to guard enum constructor so that no one else can create enum instances **/
function __() { };
/** Dynamically define a function with the same name as the enum we want to define. **/
var __enum = new Function(["__"],
"return function " + typeName + "(sentinel, name, ordinal) {" +
"if(!(sentinel instanceof __)) {" +
"throw new TypeError(\"Cannot instantiate an instance of " + typeName + ".\");" +
"}" +
"this.__name = name;" +
"this.__ordinal = ordinal;" +
"}"
)(__);
/** Private objects used to maintain enum instances for values(), and to look up enum instances for fromName() **/
var __values = [];
var __dict = {};
/** Attach values() and fromName() methods to the class itself (kind of like static methods). **/
Object.defineProperty(__enum, "values", {
value: function () {
return __values;
}
});
Object.defineProperty(__enum, "fromName", {
value: function (name) {
var __constant = __dict[name]
if (__constant) {
return __constant;
} else {
throw new TypeError(typeName + " does not have a constant with name " + name + ".");
}
}
});
/**
* The following methods are available to all instances of the enum. values() and fromName() need to be
* available to each constant, and so we will attach them on the prototype. But really, they're just
* aliases to their counterparts on the prototype.
*/
Object.defineProperty(__enum.prototype, "values", {
value: __enum.values
});
Object.defineProperty(__enum.prototype, "fromName", {
value: __enum.fromName
});
Object.defineProperty(__enum.prototype, "name", {
value: function () {
return this.__name;
}
});
Object.defineProperty(__enum.prototype, "ordinal", {
value: function () {
return this.__ordinal;
}
});
Object.defineProperty(__enum.prototype, "valueOf", {
value: function () {
return this.__name;
}
});
Object.defineProperty(__enum.prototype, "toString", {
value: function () {
return this.__name;
}
});
/**
* If constants was an array, we can the element values directly. Otherwise, we will have to use the keys
* from the constants object.
*/
var _constants = constants;
if (isObject) {
_constants = Object.keys(constants);
}
/** Iterate over all constants, create an instance of our enum for each one, and attach it to the enum type **/
_constants.forEach(function (name, ordinal) {
// Create an instance of the enum
var __constant = new __enum(new __(), name, ordinal);
// If constants was an object, we want to attach the provided attributes to the instance.
if (isObject) {
Object.keys(constants[name]).forEach(function (attr) {
Object.defineProperty(__constant, attr, {
value: constants[name][attr]
});
});
}
// Freeze the instance so that it cannot be modified.
Object.freeze(__constant);
// Attach the instance using the provided name to the enum type itself.
Object.defineProperty(__enum, name, {
value: __constant
});
// Update our private objects
__values.push(__constant);
__dict[name] = __constant;
});
/** Define a friendly toString method for the enum **/
var string = typeName + " { " + __enum.values().map(function (c) {
return c.name();
}).join(", ") + " } ";
Object.defineProperty(__enum, "toString", {
value: function () {
return string;
}
});
/** Freeze our private objects **/
Object.freeze(__values);
Object.freeze(__dict);
/** Freeze the prototype on the enum and the enum itself **/
Object.freeze(__enum.prototype);
Object.freeze(__enum);
/** Return the enum **/
return __enum;
}
return {
define: define
}
})();
var ColorEnum = {
red: {},
green: {},
blue: {}
}
You don't need to make sure you don't assign duplicate numbers to different enum values this way. A new object gets instantiated and assigned to all enum values.
IE8 does Not support freeze() method.
Source: http://kangax.github.io/compat-table/es5/, Click on "Show obsolete browsers?" on top, and check IE8 & freeze row col intersection.
In my current game project, I have used below, since few customers still use IE8:
var CONST_WILD_TYPES = {
REGULAR: 'REGULAR',
EXPANDING: 'EXPANDING',
STICKY: 'STICKY',
SHIFTING: 'SHIFTING'
};
We could also do:
var CONST_WILD_TYPES = {
REGULAR: 'RE',
EXPANDING: 'EX',
STICKY: 'ST',
SHIFTING: 'SH'
};
or even this:
var CONST_WILD_TYPES = {
REGULAR: '1',
EXPANDING: '2',
STICKY: '3',
SHIFTING: '4'
};
The last one, seems most efficient for string, it reduces your total bandwidth if you have server & client exchanging this data.
Of course, now it's your duty to make sure there are no conflicts in the data (RE, EX, etc. must be unique, also 1, 2, etc. should be unique). Note that you need to maintain these forever for backward compatibility.
Assignment:
var wildType = CONST_WILD_TYPES.REGULAR;
Comparision:
if (wildType === CONST_WILD_TYPES.REGULAR) {
// do something here
}
I wasn't satisfied with any of the answers, so I made Yet Another Enum (YEA!).
This implementation:
uses more up-to-date JS
requires just the declaration of this one class to easily create enums
has mapping by name (colors.RED), string (colors["RED"]), and index (colors[0]), but you only need to pass in the strings as an array
binds equivalent toString() and valueOf() functions to each enum object (if this is somehow not desired, one can simply remove it - small overhead for JS though)
has optional global naming/storage by name string
freezes the enum object once created so that it can't be modified
Special thanks to Andre 'Fi''s answer for some inspiration.
The codes:
class Enums {
static create({ name = undefined, items = [] }) {
let newEnum = {};
newEnum.length = items.length;
newEnum.items = items;
for (let itemIndex in items) {
//Map by name.
newEnum[items[itemIndex]] = parseInt(itemIndex, 10);
//Map by index.
newEnum[parseInt(itemIndex, 10)] = items[itemIndex];
}
newEnum.toString = Enums.enumToString.bind(newEnum);
newEnum.valueOf = newEnum.toString;
//Optional naming and global registration.
if (name != undefined) {
newEnum.name = name;
Enums[name] = newEnum;
}
//Prevent modification of the enum object.
Object.freeze(newEnum);
return newEnum;
}
static enumToString() {
return "Enum " +
(this.name != undefined ? this.name + " " : "") +
"[" + this.items.toString() + "]";
}
}
Usage:
let colors = Enums.create({
name: "COLORS",
items: [ "RED", "GREEN", "BLUE", "PORPLE" ]
});
//Global access, if named.
Enums.COLORS;
colors.items; //Array(4) [ "RED", "GREEN", "BLUE", "PORPLE" ]
colors.length; //4
colors.RED; //0
colors.GREEN; //1
colors.BLUE; //2
colors.PORPLE; //3
colors[0]; //"RED"
colors[1]; //"GREEN"
colors[2]; //"BLUE"
colors[3]; //"PORPLE"
colors.toString(); //"Enum COLORS [RED,GREEN,BLUE,PORPLE]"
//Enum frozen, makes it a real enum.
colors.RED = 9001;
colors.RED; //0
Simplest solution:
Create
var Status = Object.freeze({
"Connecting":0,
"Ready":1,
"Loading":2,
"Processing": 3
});
Get Value
console.log(Status.Ready) // 1
Get Key
console.log(Object.keys(Status)[Status.Ready]) // Ready
es7 way, (iterator, freeze), usage:
const ThreeWiseMen = new Enum('Melchior', 'Caspar', 'Balthazar')
for (let name of ThreeWiseMen)
console.log(name)
// with a given key
let key = ThreeWiseMen.Melchior
console.log(key in ThreeWiseMen) // true (string conversion, also true: 'Melchior' in ThreeWiseMen)
for (let entry from key.enum)
console.log(entry)
// prevent alteration (throws TypeError in strict mode)
ThreeWiseMen.Me = 'Me too!'
ThreeWiseMen.Melchior.name = 'Foo'
code:
class EnumKey {
constructor(props) { Object.freeze(Object.assign(this, props)) }
toString() { return this.name }
}
export class Enum {
constructor(...keys) {
for (let [index, key] of keys.entries()) {
Object.defineProperty(this, key, {
value: new EnumKey({ name:key, index, enum:this }),
enumerable: true,
})
}
Object.freeze(this)
}
*[Symbol.iterator]() {
for (let key of Object.keys(this))
yield this[key]
}
toString() { return [...this].join(', ') }
}
This can be useful:
const [CATS, DOGS, BIRDS] = ENUM();
The implementation is simple and efficient:
function * ENUM(count=1) { while(true) yield count++ }
A generator can yield the exact sequence of integers required, without knowing how many constants there are. It can also support an optional argument that specifies which (possibly negative) number to start from (defaulting to 1).
A quick and simple way would be :
var Colors = function(){
return {
'WHITE':0,
'BLACK':1,
'RED':2,
'GREEN':3
}
}();
console.log(Colors.WHITE) //this prints out "0"
Here's a couple different ways to implement TypeScript enums.
The easiest way is to just iterate over an object, adding inverted key-value pairs to the object. The only drawback is that you must manually set the value for each member.
function _enum(list) {
for (var key in list) {
list[list[key] = list[key]] = key;
}
return Object.freeze(list);
}
var Color = _enum({
Red: 0,
Green: 5,
Blue: 2
});
// Color โ†’ {0: "Red", 2: "Blue", 5: "Green", "Red": 0, "Green": 5, "Blue": 2}
// Color.Red โ†’ 0
// Color.Green โ†’ 5
// Color.Blue โ†’ 2
// Color[5] โ†’ Green
// Color.Blue > Color.Green โ†’ false
And here's a lodash mixin to create an enum using a string. While this version is a little bit more involved, it does the numbering automatically for you. All the lodash methods used in this example have a regular JavaScript equivalent, so you can easily switch them out if you want.
function enum() {
var key, val = -1, list = {};
_.reduce(_.toArray(arguments), function(result, kvp) {
kvp = kvp.split("=");
key = _.trim(kvp[0]);
val = _.parseInt(kvp[1]) || ++val;
result[result[val] = key] = val;
return result;
}, list);
return Object.freeze(list);
}
// Add enum to lodash
_.mixin({ "enum": enum });
var Color = _.enum(
"Red",
"Green",
"Blue = 5",
"Yellow",
"Purple = 20",
"Gray"
);
// Color.Red โ†’ 0
// Color.Green โ†’ 1
// Color.Blue โ†’ 5
// Color.Yellow โ†’ 6
// Color.Purple โ†’ 20
// Color.Gray โ†’ 21
// Color[5] โ†’ Blue
I've just published an NPM package gen_enum allows you to create Enum data structure in Javascript quickly:
var genEnum = require('gen_enum');
var AppMode = genEnum('SIGN_UP, LOG_IN, FORGOT_PASSWORD');
var curMode = AppMode.LOG_IN;
console.log(curMode.isLogIn()); // output true
console.log(curMode.isSignUp()); // output false
console.log(curMode.isForgotPassword()); // output false
One nice thing about this little tool is in modern environment (including nodejs and IE 9+ browsers) the returned Enum object is immutable.
For more information please checkout https://github.com/greenlaw110/enumjs
Updates
I obsolete gen_enum package and merge the function into constjs package, which provides more features including immutable objects, JSON string deserialization, string constants and bitmap generation etc. Checkout https://www.npmjs.com/package/constjs for more information
To upgrade from gen_enum to constjs just change the statement
var genEnum = require('gen_enum');
to
var genEnum = require('constjs').enum;
I've made an Enum class that can fetch values AND names at O(1). It can also generate an Object Array containing all Names and Values.
function Enum(obj) {
// Names must be unique, Values do not.
// Putting same values for different Names is risky for this implementation
this._reserved = {
_namesObj: {},
_objArr: [],
_namesArr: [],
_valuesArr: [],
_selectOptionsHTML: ""
};
for (k in obj) {
if (obj.hasOwnProperty(k)) {
this[k] = obj[k];
this._reserved._namesObj[obj[k]] = k;
}
}
}
(function () {
this.GetName = function (val) {
if (typeof this._reserved._namesObj[val] === "undefined")
return null;
return this._reserved._namesObj[val];
};
this.GetValue = function (name) {
if (typeof this[name] === "undefined")
return null;
return this[name];
};
this.GetObjArr = function () {
if (this._reserved._objArr.length == 0) {
var arr = [];
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
arr.push({
Name: k,
Value: this[k]
});
}
this._reserved._objArr = arr;
}
return this._reserved._objArr;
};
this.GetNamesArr = function () {
if (this._reserved._namesArr.length == 0) {
var arr = [];
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
arr.push(k);
}
this._reserved._namesArr = arr;
}
return this._reserved._namesArr;
};
this.GetValuesArr = function () {
if (this._reserved._valuesArr.length == 0) {
var arr = [];
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
arr.push(this[k]);
}
this._reserved._valuesArr = arr;
}
return this._reserved._valuesArr;
};
this.GetSelectOptionsHTML = function () {
if (this._reserved._selectOptionsHTML.length == 0) {
var html = "";
for (k in this) {
if (this.hasOwnProperty(k))
if (k != "_reserved")
html += "<option value='" + this[k] + "'>" + k + "</option>";
}
this._reserved._selectOptionsHTML = html;
}
return this._reserved._selectOptionsHTML;
};
}).call(Enum.prototype);
You can init'd it like this:
var enum1 = new Enum({
item1: 0,
item2: 1,
item3: 2
});
To fetch a value (like Enums in C#):
var val2 = enum1.item2;
To fetch a name for a value (can be ambiguous when putting the same value for different names):
var name1 = enum1.GetName(0); // "item1"
To get an array with each name & value in an object:
var arr = enum1.GetObjArr();
Will generate:
[{ Name: "item1", Value: 0}, { ... }, ... ]
You can also get the html select options readily:
var html = enum1.GetSelectOptionsHTML();
Which holds:
"<option value='0'>item1</option>..."
Even though only static methods (and not static properties) are supported in ES2015 (see here as well, ยง15.2.2.2), curiously you can use the below with Babel with the es2015 preset:
class CellState {
v: string;
constructor(v: string) {
this.v = v;
Object.freeze(this);
}
static EMPTY = new CellState('e');
static OCCUPIED = new CellState('o');
static HIGHLIGHTED = new CellState('h');
static values = function(): Array<CellState> {
const rv = [];
rv.push(CellState.EMPTY);
rv.push(CellState.OCCUPIED);
rv.push(CellState.HIGHLIGHTED);
return rv;
}
}
Object.freeze(CellState);
I found this to be working as expected even across modules (e.g. importing the CellState enum from another module) and also when I import a module using Webpack.
The advantage this method has over most other answers is that you can use it alongside a static type checker (e.g. Flow) and you can assert, at development time using static type checking, that your variables, parameters, etc. are of the specific CellState "enum" rather than some other enum (which would be impossible to distinguish if you used generic objects or symbols).
update
The above code has a deficiency in that it allows one to create additional objects of type CellState (even though one can't assign them to the static fields of CellState since it's frozen). Still, the below more refined code offers the following advantages:
no more objects of type CellState may be created
you are guaranteed that no two enum instances are assigned the same code
utility method to get the enum back from a string representation
the values function that returns all instances of the enum does not have to create the return value in the above, manual (and error-prone) way.
'use strict';
class Status {
constructor(code, displayName = code) {
if (Status.INSTANCES.has(code))
throw new Error(`duplicate code value: [${code}]`);
if (!Status.canCreateMoreInstances)
throw new Error(`attempt to call constructor(${code}`+
`, ${displayName}) after all static instances have been created`);
this.code = code;
this.displayName = displayName;
Object.freeze(this);
Status.INSTANCES.set(this.code, this);
}
toString() {
return `[code: ${this.code}, displayName: ${this.displayName}]`;
}
static INSTANCES = new Map();
static canCreateMoreInstances = true;
// the values:
static ARCHIVED = new Status('Archived');
static OBSERVED = new Status('Observed');
static SCHEDULED = new Status('Scheduled');
static UNOBSERVED = new Status('Unobserved');
static UNTRIGGERED = new Status('Untriggered');
static values = function() {
return Array.from(Status.INSTANCES.values());
}
static fromCode(code) {
if (!Status.INSTANCES.has(code))
throw new Error(`unknown code: ${code}`);
else
return Status.INSTANCES.get(code);
}
}
Status.canCreateMoreInstances = false;
Object.freeze(Status);
exports.Status = Status;
This is how Typescript translates it's enum into Javascript:
var makeEnum = function(obj) {
obj[ obj['Active'] = 1 ] = 'Active';
obj[ obj['Closed'] = 2 ] = 'Closed';
obj[ obj['Deleted'] = 3 ] = 'Deleted';
}
Now:
makeEnum( NewObj = {} )
// => {1: "Active", 2: "Closed", 3: "Deleted", Active: 1, Closed: 2, Deleted: 3}
At first I was confused why obj[1] returns 'Active', but then realised that its dead simple - Assignment operator assigns value and then returns it:
obj['foo'] = 1
// => 1
You can do something like this
var Enum = (function(foo) {
var EnumItem = function(item){
if(typeof item == "string"){
this.name = item;
} else {
this.name = item.name;
}
}
EnumItem.prototype = new String("DEFAULT");
EnumItem.prototype.toString = function(){
return this.name;
}
EnumItem.prototype.equals = function(item){
if(typeof item == "string"){
return this.name == item;
} else {
return this == item && this.name == item.name;
}
}
function Enum() {
this.add.apply(this, arguments);
Object.freeze(this);
}
Enum.prototype.add = function() {
for (var i in arguments) {
var enumItem = new EnumItem(arguments[i]);
this[enumItem.name] = enumItem;
}
};
Enum.prototype.toList = function() {
return Object.keys(this);
};
foo.Enum = Enum;
return Enum;
})(this);
var STATUS = new Enum("CLOSED","PENDING", { name : "CONFIRMED", ackd : true });
var STATE = new Enum("CLOSED","PENDING","CONFIRMED",{ name : "STARTED"},{ name : "PROCESSING"});
As defined in this library.
https://github.com/webmodule/foo/blob/master/foo.js#L217
Complete example
https://gist.github.com/lnt/bb13a2fd63cdb8bce85fd62965a20026

Categories