Optimizing my KnockOutJS function - From Ugly Code to Clean, How? - javascript

Function Description
This function is supposed to simply replace an item in my observable array.
I am accepting 3 parameters:
findBy = Whether to locate the source index by "id" or "name" property.
cmpVal = The value we are searching the array for
objNewItem = The new item. This case ex, { id: "12" , name: "NewName" }
Then I am deleting that index from the array, and finally pushing the new object into the array.
I had no luck using the replace function from Knockout, so I had to write my own.
I realize this may be the ugliest code on the internet. That's why I am deferring to you professionals here. :)
/* Update A Station
*
* #param findBy string Property to find ( "id" or "name")
* #param cmpVal string Value to compare against
* #param objNewItem object The new object replacing old
* */
self.modifyStation = function(findBy, cmpVal, objNewItem){
var sourceIndex;
var oldId;
/* Find Index Of Old Station */
var c = -1;
ko.utils.arrayForEach(this.station(), function(item) {
c++;
switch (findBy) {
case "id":
var value = item.id();
if(value == cmpVal){
sourceIndex = c;
oldId = item.id();
}
break;
case "name":
var value = item.name();
if(value == cmpVal){
sourceIndex = c;
oldId = item.id();
}
break;
}
});
/* Remove Old */
self.station().splice(sourceIndex,1);
/* Insert New Station
* [For Now] not allowing updating of ID. Only
* can update the other properties (yes, I realize that
* only leaves "name", but more will be added )
*/
objNewItem.id = oldId; // Put old ID back in
self.station.push(objNewItem);
}
Note: I am not allowing them to edit the ID for now.
Can anyone help me clean this up? I am smart enough to know its not efficient, but I don't know how else to optimize it.
Any advice would be appreciated. Thank you!
John

Ok so...
First of we will create a function that will contain all the logic, from this example on you can do anything with it. The most proper way would be to extend your 'observableArray' directly on knockout, but i am not going to get this one this far now :P
function ReplaceInObservableArray(obsArray, prop, cmpVal, newItem){
// use the fact that you can get the value of the property by treating the object as the dictionary that it is
// so you do not need to program for specific properties for different array model types
var foundItems = obsArray().filter(function(i){
return ko.utils.unwrapObservable(i[prop]) == cmpVal;
});
if(foundItems.length > 1)
{
// handle what happens when the property you are comparing is not unique on your list. More than one items exists with this comparison run
// you should throw or improve this sample further with this case implementation.
} else if(foundItems.length == 0)
{
// handle what happens when there is nothing found with what you are searching with.
} else {
// replace at the same index rather than pushing, so you dont move any other items on the array
// use the frameworks built in method to make the replacement
obsArray.replace(foundItems[0], newItem);
}
}
var demoArray = ko.observableArray([{ id : 1, name : 'test1' },{id : 2, name : 'test2' },{ id : 3, name : 'test3'},{id : 4, name : 'test4'}]);
ReplaceInObservableArray(demoArray,'id', 1, {id : 1, name : 'test111'});
ReplaceInObservableArray(demoArray,'name', 'test3', {id : 3, name : 'test3333'});
console.log(demoArray());

Related

Mapping data with dynamic variables

I am having a little trouble trying to achieve something. So I have some data
let data = [
{
"ID": 123456,
"Date": "2012-01-01",
"Irrelevant_Column_1": 123,
"Irrelevant_Column_2": 234,
"Irrelevant_Column_3": 345,
"Irrelevant_Column_4": 456
},
...
]
And I wanted to remove the irrelevant columns. So someone suggested using map
data = data.map(element => ({ID: element.ID, Date: element.Date}))
The problem is, I dont want to define the columns. I have the user select the columns to keep, and assign them to a variable. I can then do something like
let selectedId = this.selectedIdCol;
The issue is, I am unable to now use this within the map. I am trying
let selectedId = this.selectedIdCol;
this.parsed_csv = data.map(element => (
{ID: element.selectedId, Date: element.Date}
));
But that does not seem to work, just returns the date. Also, my IDE is saying that the variable is unused. So how can I use the selectedId variable as part of the map function?
Thanks
You can do using Bracket notation notation and helper function
Whenever you want to use variable to access property you need to use [] notation.
let data = [{"ID": 123456,"Date": "2012-01-01","column_1": 123,"column_2": 234,"column_3": 345,"column_4": 456},{"ID": 123456,"Date": "2018-10-01", "column_1": 123,"column_2": 234,"column_3": 345,"column_4": 46},]
function selectDesired(data,propName1,propName2){
return data.map(e=> ({[propName1]: e[propName1], [propName2]: e[propName2]}))
}
console.log(selectDesired(data, 'Date', 'column_4'))
The basic technique is illustrated here, assuming that the user's selected column_name is "ID"
let data = [
{
"ID": 123456,
"Date": "2012-01-01",
"Irrelevant_Column_1": 123,
"Irrelevant_Column_2": 234,
"Irrelevant_Column_3": 345,
"Irrelevant_Column_4": 456
}
];
let column_name = "ID";
let curated = data.map(element=>({[column_name]: element[column_name]}));
console.log(curated)
If you are wanting the user to be able to multi-select their columns,(assuming data from above is still in scope)
let user_selection = ["ID","Date"];
let curated = data.map(
(element)=>
{
let item = {};
user_selection.forEach(
(property)=>
{
item[property] = element[property];
}
return item;
}
);
To set up a function that can handle multiple calling situations without having a monstrously hack-and-patched source history, set up the function's signature to receive a spread list of properties.
If you wish to extend the capabilities to accept
a csv property list
an array of property names delivered directly
an array of property names
you can assume the properties argument in the signature to be an iterable of property groupings, having the most basic grouping be a singleton.
Commentary embedded within the sample code to expound in more detail
var getProjection = (data,...properties) =>
{
//+=================================================+
// Initialize the projection which will be returned
//+=================================================+
let projection = {};
//+=================================================+
// Set up the property mapping func
//+=================================================+
let safe_assign = (source, target ,propertyDesignator)=>
{
if(source[propertyDesignator])
{
target[propertyDesignator] = source[propertyDesignator];
}
};
//+=====================================================+
// Iterate the properties list, assuming each element to
// be a property grouping
//+=====================================================+
properties.forEach(
(propertyGroup)=>
{
//+-----------------------------------------------+
// If the propertyGroup is not an array, perform
// direct assignment
//+-----------------------------------------------+
if(!Array.isArray(propertyGroup))
{
//+-------------------------------------------+
//Only map the requested property if it exists
//+-------------------------------------------+
safe_assign(data,projection,propertyGroup);
}
//+-----------------------------------------------+
// If the propertyGroup *is* an array, iterate it
// This technique obviously assumes that your
// property groupings are only allowed to be one
// level deep. This is for accommodating distinct
// calling conventions, not for supporting a deeply
// nested object graph. For a deeper object graph,
// the technique would largely be the same, but
// you would need to recurse.
//+-----------------------------------------------+
if( Array.isArray(propertyGroup))
{
propertyGroup.forEach(
(property)=>
{
safe_assign(data,projection,property);
}
}
}
);
//+===================================+
// Return your projection
//+===================================+
return projection;
};
//+--------------------------------------+
//Now let's test
//+--------------------------------------+
let data = [
{ID:1,Foo:"Foo1",Bar:"Bar1",Baz:"Inga"},
{ID:2,Foo:"Foo2",Bar:"Bar2",Baz:"Ooka"},
{ID:3,Foo:"Foo3",Bar:"Bar3",Baz:"oinga",Floppy:"Floop"},
{ID:4,Foo:"Foo4",Good:"Boi",Bar:"Bar3"Baz:"Baz"}
];
//***************************************
//tests
//***************************************
var projection1 = getProjection(data.find(first=>first),"ID","Baz"));//=>{ID:1,Baz:"Inga"}
var projection2 = getProjection(data[0],["ID","Baz"]);//=>{ID:1,Baz:"Inga"}
var projection3 = getProjection(data[0],...["ID","Baz"]);//=>{ID:1,Baz:"Inga"}
var user_selected_properties = ["ID","Good","Baz"];
var projections = data.map(element=>getProjection(element,user_selected_properties));
//+=====================================+
// projections =
// [
// {ID:1,Baz:"Inga"},
// {ID:2,Baz:"Ooka"},
// {ID:3,Baz:"oinga"},
// {ID:4,Good:"Boi",Baz:"Baz"}
// ];
//+=====================================+

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.

Get the lower integer id not already used in Javascript

I have a list of JS objects defined by an integer ID.
objects = [{
id: 0,
type: 'null'
}, {
id: 1,
type: 'foo'
}, {
id: 2,
type: 'bar'
}];
I implemented a function to remove an element from my list :
removeObject = function(o){
objects.splice(objects.indexOf(o), 1);
}
My problem is that I need to create a function to add a new item in my list with a id not already used (for example the lower positive integer not present in the list).
I tried to do something like that but it did not work when I remove the object 0 (for example).
addObject = function(type){
objects.push({
id: objects.length,
type: type
});
};
How can I do this ?
EDIT 1
According to your answers, I assume that the best solution in term of performance is to just use a topId which is always incremented when I add a new object in my list.
But that do not answer to my requierement. Actually I think that #X-Pippes response could be good.
Should I do someting like that :
objects = [{
id: 0,
type: 'null'
}, {
id: 1,
type: 'foo'
}, {
id: 2,
type: 'bar'
}];
// Init available ids list with the default value
availableIds = [objects.length];
removeObject = function(o){
// Remove the object from the list
objects.splice(objects.indexOf(o), 1);
// Add its id to the available ids list
availableIds.push(o.id);
}
addObject = function(type){
// Get lower id available
var newId = Math.min.apply(Math,availableIds);
// Push the new object with the id retrieved
objects.push({
id: newId,
type: type
});
// Remove used id from the available ids list
availableIds.splice(availableIds.indexOf(newId), 1);
// Add a default id if available list is empty
if(availableIds.length < 1) availableIds.push(objects.length);
};
if you remove for instance 0 and the next addObject is 0 you have to do something like:
keep a list [initial empty] with every ID removed. When you need to add a new one, pick the shorter, add and delete from list.
Also keep a var with the biggest ID added. If the previous list is empty, add +1 to the var and addObject with that id
Use the correct structures. A JavaScript object will do the job. It guarantees that you only get one item for key, you can look up and remove by key in probably O(1)ish. No point trying to re-implement it in a less efficient manner, which will be O(n) lookup.
var structure = {
objects : {},
topId : 0
}
structure.add = function(item) {
var id = this.topId ++;
structure.objects[id] = item;
}
structure.add("thing")
structure.add("other thing")
structure.add("another thing")
structure.objects
>>> Object {0: "thing", 1: "other thing", 2: "another thing"}
structure.objects[1]
>> "other thing"
Then the normal index operations to get/set/delete.
If you use that function then you have an invariant (guarantee) on your data structure that you won't use the same ID twice.
You need a function to find the first free number:
addObject = function(type){
objects.push({
id: firstOpenIndex(),
type: type
});
};
firstOpenIndex = function() {
for(var idx = 0; true; i++) {
var found = false;
for(var o in objects) {
if (objects[o].id == idx) {
found = true;
break;
}
}
if (!found) return idx;
}
}
In Javascript MaxInt is 9007199254740992. Why not just keep incrementing?
You can and probably should just use an array(s) like:
objects.type=['null','foo','bar'];
to add an object see:
How to append something to an array?
to find a value: var index = objects.type.indexOf('foo');
to find 1st empty field var index = objects.type.indexOf(''); which you can use to find the element for adding (if index is -1 use objects.type.length) if you "delete" an element by setting it to "" or... unless you have specific reason for keeping the "ID" static (in this case the array index), remove the element and only append new ones to the end
to remove an element see:
How do I remove a particular element from an array in JavaScript?
which will allow you to just push/append the next data.
if you need a new object array with empty fields to fill because you get new data to track:
object.newField=new Array(objects.type.length);
If you get to this point where your object contains multiple arrays, you will probably want to create functions for insert/add and delete/remove, so you don't do an operation on 1 and not the other.
Everything is already built in (read likely already pretty fast) and you don't need to reinvent constructors for your really cool object type.

Creating a nested JSON object dynamically?

I am almost there with this but cannot seem to get this functionality going as planned.
I have json 'jsonData' it contains formula of different terms
"jsonData":{
"a" : "b + c",
"b" : "d + e",
"d" : "h + i",
"c" : "f + g"
}
What I am trying to do is to have a function pass one arguments 'mainItem'(ie. one of the key in 'jsonData' for example a in 'jsonData'). Within this function it will get the formula from the json data(for example a is the 'mainitem' and b + c is the formula) and check the dependency of child component of the formula i.e it will check whether b and c have any dependency down the line in json data. If it has any dependency it will be added as a child component to the parent for example if b have a formula in json data. b will be added as the 'mainitem' in the child component of the parent 'mainitem' a. At the end of the code, this is what I would like to get.
{
mainitem : "a",
formula : "b+c",
childComponent: {
mainitem: "b",
formula : "d+e",
childcomponent: {
mainitem: "d",
formula : "h+i"
}
},
{
mainitem: "c",
formula : "f+g"
},
}
The issue is that I am able to create the parent object. But I have no idea how to create the child component for the parent and If the child component have sub child it will also embedded as the child of the child component and so on. It is like a parent child hierarchy series
function getJson(mainItem) {
var json = {};
json['mainitem'] = mainItem;
$.each(jsonData, function(key, value){
if(mainitem == key){
json['formula'] = value;
}
})
}
Any insight into this would highly be appreciated. Thank you.
You need/could to write a recursive function that splits up the "formula" into each composing component/item and then check each component/item for their dependencies.
Here is a solution for you: http://jsfiddle.net/mqchen/4x7cD/
function getJson(item, data) {
if(!data["jsonData"].hasOwnProperty(item)) return null;
var out = {
mainItem: item,
formula: data["jsonData"][item]
};
// Break up formula
var components = out.formula.split(" ");
for(var i = 0; i < components.length; i++) {
var child = getJson(components[i], data); // Recursive call to get childComponents
if(child !== null) {
out["childComponent"] = out["childComponent"] == undefined ? [] : out["childComponent"];
out["childComponent"].push(child);
}
}
return out;
}
// Call it
getJson("a", data)
Note: It does not consider circular dependencies, i.e. if you have a: "b + c", b: "d + a".
This is a dependency problem. I can already tell you ahead of time that you will need to figure out a way to handle circular dependencies (you don't want to get thrown into an infinite loop/recursion when trying to generate the output). Let's go through the basic algorithm. What are some things we need?
We need a way to parse the formulas so that they're meaningful. For this example, I'm gonna assume that we'll get an input that's of the form a + b + c, where I only expect the item and + to delimit each item.
We need a way to recurse down an item's dependencies to create nested childcomponents.
// assuming jsonData is available in this scope
function getStructure (elem) {
elem = $.trim(elem);
// handling an element that doesn't exist in jsonData
if (!jsonData[elem]) {
return {
'mainitem': elem,
'formula': 'none' // or whatever you want to put
};
}
var result = {},
formula = jsonData[elem],
children = formula.split('+'),
i;
result['mainitem'] = elem;
result['formula'] = formula;
// or however you want to store the child components
result['childComponent'] = [];
for (i = 0; i < children.length; i += 1) {
result['childComponent'].push(getStructure(children[i]));
}
return result;
}
Yeah this is some sort of building Syntax Tree as in classic parser/compiler problem.
Any ways I have written this simple recursive function that does what you want. Though if your goal is to build some sort of parser then you must think of following parser / compiler building principles since that will keep things manageable and graspable once functions start growing.
function getJson(mainitem,outjson)
{
formula = jsonData[mainitem];
outjson.mainitem = mainitem;
if (formula != null)
{
outjson.formula = formula;
var firstItem = formula.toString().charAt(0);
var secondItem = formula.charAt(formula.length - 1);
outjson.firstchild = {};
outjson.secondchild = {};
getJson(firstItem, outjson.firstchild);
getJson(secondItem, outjson.secondchild);
}
}
What all you have to do is to create an empty object and pass it to getJson() along with the operand in problem i.e. mainitem:
var outjson = {};
getJson("a", outjson);
I have used JSON library to convert outjson object to JSON text.
Also I have logged this outjson so that you can examine it in the embedded firebug lites' Console window.
Find it at JSFiddle.
There is approved answer already but here are my 5 cents.
as #Mahesha999 said you need to build "Syntax Tree as in classic parser/compiler".
For some theory + examples look at those videos
They are focused on antlr but also contains a lot theory about parsers. Also antlr have javascript plugin that can be used.
I think it's better than any expression evaluation.

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