What is the preferred syntax for defining enums in

2019-01-01 11:47发布

What is the preferred syntax for defining enums in JavaScript? Something like:

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

// later on

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

Or is there a more preferable idiom?

30条回答
有味是清欢
2楼-- · 2019-01-01 12:17

Even though only static methods (and not static properties) are supported in ES2015 (see here as well, §15.2.2.2), curiously you can use the below with Babel with the es2015 preset:

class CellState {
    v: string;
    constructor(v: string) {
        this.v = v;
        Object.freeze(this);
    }
    static EMPTY       = new CellState('e');
    static OCCUPIED    = new CellState('o');
    static HIGHLIGHTED = new CellState('h');
    static values      = function(): Array<CellState> {
        const rv = [];
        rv.push(CellState.EMPTY);
        rv.push(CellState.OCCUPIED);
        rv.push(CellState.HIGHLIGHTED);
        return rv;
    }
}
Object.freeze(CellState);

I found this to be working as expected even across modules (e.g. importing the CellState enum from another module) and also when I import a module using Webpack.

The advantage this method has over most other answers is that you can use it alongside a static type checker (e.g. Flow) and you can assert, at development time using static type checking, that your variables, parameters, etc. are of the specific CellState "enum" rather than some other enum (which would be impossible to distinguish if you used generic objects or symbols).

update

The above code has a deficiency in that it allows one to create additional objects of type CellState (even though one can't assign them to the static fields of CellState since it's frozen). Still, the below more refined code offers the following advantages:

  1. no more objects of type CellState may be created
  2. you are guaranteed that no two enum instances are assigned the same code
  3. utility method to get the enum back from a string representation
  4. the values function that returns all instances of the enum does not have to create the return value in the above, manual (and error-prone) way.

    'use strict';
    
    class Status {
    
    constructor(code, displayName = code) {
        if (Status.INSTANCES.has(code))
            throw new Error(`duplicate code value: [${code}]`);
        if (!Status.canCreateMoreInstances)
            throw new Error(`attempt to call constructor(${code}`+
           `, ${displayName}) after all static instances have been created`);
        this.code        = code;
        this.displayName = displayName;
        Object.freeze(this);
        Status.INSTANCES.set(this.code, this);
    }
    
    toString() {
        return `[code: ${this.code}, displayName: ${this.displayName}]`;
    }
    static INSTANCES   = new Map();
    static canCreateMoreInstances      = true;
    
    // the values:
    static ARCHIVED    = new Status('Archived');
    static OBSERVED    = new Status('Observed');
    static SCHEDULED   = new Status('Scheduled');
    static UNOBSERVED  = new Status('Unobserved');
    static UNTRIGGERED = new Status('Untriggered');
    
    static values      = function() {
        return Array.from(Status.INSTANCES.values());
    }
    
    static fromCode(code) {
        if (!Status.INSTANCES.has(code))
            throw new Error(`unknown code: ${code}`);
        else
            return Status.INSTANCES.get(code);
    }
    }
    
    Status.canCreateMoreInstances = false;
    Object.freeze(Status);
    exports.Status = Status;
    
查看更多
明月照影归
3楼-- · 2019-01-01 12:17

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

查看更多
梦该遗忘
4楼-- · 2019-01-01 12:18

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
查看更多
余生无你
5楼-- · 2019-01-01 12:20

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()
查看更多
泛滥B
6楼-- · 2019-01-01 12:22

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
查看更多
看淡一切
7楼-- · 2019-01-01 12:23

The "Perfered Syntax" by most people has already been listed above. However, there is a major overarching problem: performance. Not a single one of the above answers is very performant in the slightest and they all bloat your code size to the extreme. For real performance, ease of reading code, and an unprecedented reduction in code size by minification, this is the correct way to do enumerations.

const ENUM_COLORENUM_RED   = 0,
      ENUM_COLORENUM_GREEN = 1,
      ENUM_COLORENUM_BLUE  = 2,
      ENUMLEN_COLORENUM    = 3;

// later on

if(currentColor == ENUM_COLORENUM_RED) {
   // whatever
}

Additionally, this syntax allows for clear and concise class extending as seen below.

(Length: 2,450 bytes)

(function(window){
    "use strict";
    var parseInt = window.parseInt

    const ENUM_PIXELCOLOR_TYPE = 0, // is a ENUM_PIXELTYPE
          ENUMLEN_PIXELCOLOR   = 1,
          ENUM_SOLIDCOLOR_R    = ENUMLEN_PIXELCOLOR+0,
          ENUM_SOLIDCOLOR_G    = ENUMLEN_PIXELCOLOR+1,
          ENUM_SOLIDCOLOR_B    = ENUMLEN_PIXELCOLOR+2,
          ENUMLEN_SOLIDCOLOR   = ENUMLEN_PIXELCOLOR+3,
          ENUM_ALPHACOLOR_R    = ENUMLEN_PIXELCOLOR+0,
          ENUM_ALPHACOLOR_G    = ENUMLEN_PIXELCOLOR+1,
          ENUM_ALPHACOLOR_B    = ENUMLEN_PIXELCOLOR+2,
          ENUM_ALPHACOLOR_A    = ENUMLEN_PIXELCOLOR+3,
          ENUMLEN_ALPHACOLOR   = ENUMLEN_PIXELCOLOR+4,
          ENUM_PIXELTYPE_SOLID = 0,
          ENUM_PIXELTYPE_ALPHA = 1,
          ENUM_PIXELTYPE_UNKNOWN = 2,
          ENUMLEN_PIXELTYPE    = 2;

    function parseHexColor(rawstr) {
        rawstr = rawstr.trim().substring(1);
        var result = [];
        if (rawstr.length === 8) {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
            result[ENUM_ALPHACOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[ENUM_ALPHACOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[ENUM_ALPHACOLOR_B] = parseInt(rawstr.substring(4,6), 16);
            result[ENUM_ALPHACOLOR_A] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 4) {
            result[ENUM_ALPHACOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[ENUM_ALPHACOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[ENUM_ALPHACOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
            result[ENUM_ALPHACOLOR_A] = parseInt(rawstr[3], 16) * 0x11;
        } else if (rawstr.length === 6) {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[ENUM_SOLIDCOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[ENUM_SOLIDCOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[ENUM_SOLIDCOLOR_B] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 3) {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[ENUM_SOLIDCOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[ENUM_SOLIDCOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[ENUM_SOLIDCOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
        } else {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_UNKNOWN;
        }
        return result;
    }

    // the red component of green
    console.log(parseHexColor("#0f0")[ENUM_SOLIDCOLOR_R]);
    // the alpha of transparent purple
    console.log(parseHexColor("#f0f7")[ENUM_ALPHACOLOR_A]); 
    // the enumerated array for turquoise
    console.log(parseHexColor("#40E0D0"));
})(self);

Some may say that this is less practical than other solutions: it waists tons of space, it takes a long time to write, and its not coated with sugar syntax. Yes, 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 run super duper fast and be super duper small. Observe.

(Length: 605 bytes)

'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);

Now, let us see how big the equivalent file would be without any of these enumerations.

Source Without These Enumerations (length: 1,973 bytes (477 bytes shorter!))
Minified Without These Enumerations (length: 843 bytes (238 bytes longer))

As seen, without enumerations, the source code is shorter at the cost of a larger minified code. I do not know about you, I know for sure that I don't like to incorporate source code into the end product, making this enumeration way far superior insomuch that it results in smaller file sizes. Add to that this form of enumeration being way faster. Indeed, this form of enumeration is the way to go.

查看更多
登录 后发表回答