当前位置:  开发笔记 > 编程语言 > 正文

在JavaScript中定义枚举的首选语法是什么?

如何解决《在JavaScript中定义枚举的首选语法是什么?》经验,为你挑选了18个好方法。

在JavaScript中定义枚举的首选语法是什么?就像是:

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

// later on

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

还是有更优选的成语?



1> Artur Czajka..:

由于1.8.5可以密封和冻结对象,因此将上面定义为:

const DaysEnum = Object.freeze({"monday":1, "tuesday":2, "wednesday":3, ...})

要么

const DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Object.freeze(DaysEnum)

瞧!JS枚举.

但是,这并不会阻止您为变量分配不需要的值,这通常是枚举的主要目标:

let day = DaysEnum.tuesday
day = 298832342 // goes through without any errors

确保更强的类型安全性(使用枚举或其他方式)的一种方法是使用TypeScript或Flow等工具.

资源

不需要引用,但我保持它们的一致性.


这是2012年的正确答案.更简单:`var DaysEnum = Object.freeze({monday:{},tuesday:{},...});`.您不需要指定id,只需使用空对象来比较枚举.`if(incommingEnum === DaysEnum.monday)// incommingEnum是星期一`
为了向后兼容,`if(Object.freeze){Object.freeze(DaysEnum); }`
我想指出做`({monday:{},`等等意味着如果你通过stringify将该对象转换为JSON,你将获得`[{"day":{}}]``isn'要工作了.
@Supuhstar我现在对这个问题的看法是不同的.不要使用freeze(),它完全没用,浪费时间做"愚蠢"的事情.如果你想暴露一个枚举,只需要公开:`var DaysEnum = {"monday":1,"tuesday":2,"wednesday":3,...}`.比较之前评论中的对象比比较数字要快得多.
根据维基百科(http://en.wikipedia.org/wiki/JavaScript#Versions),它适用于Firefox 4,IE 9,Opera 11.60,我知道它适用于Chrome.
我在做的时候注意到的问题:({monday:{},tuesday:{}等等,在测试中,当它失败时你没有得到非常有用的消息.例如:AssertionError:期望{}等于{}
@StijndeWitt我很确定stringify将一个对象变成"propname":"propvalue" - 在这种情况下,prop值是一个空对象(如果传入的对象看起来像{day:DaysEnum.monday}).
就我而言,这是正确的答案.它简单易读.但最重要的是,它可以阻止其他人使用枚举值,这是本机支持它们的语言中枚举的基本属性.
@Supuhstar - 使用序数超出了枚举结构的目的.枚举应严格用于平等.将值设置为空对象可能会更慢,但它可以防止对枚举的滥用 - 例如,使用序数,开发人员会试图使用枚举进行排序.另一种结构更适合.

2> Gareth..:

这不是一个很好的答案,但我会说,个人工作得很好

话虽如此,因为值是什么并不重要(你使用了0,1,2),我会使用一个有意义的字符串,以防你想输出当前值.


这是在另一个答案中说明的,但由于这个答案是公认的答案,我将在此发布.OP的解决方案是正确的.但是,如果与`Object.freeze()`一起使用,它会更好.这将阻止其他代码更改枚举值.示例:`var ColorEnum = Object.freeze({RED:0,GREEN:1,BLUE:2});`
@TolgaE谢谢你的图书馆!它激励我不仅将其煮至最低限度,而且还添加了几个功能!我已经把你们分到了这里:https://github.com/BlueHuskyStudios/Micro-JS-Enum
@Supuhstar太棒了!我很高兴你可以使用它..如果你想将它合并到这个库中,请随意提出拉取请求,然后我可以更新npm库
我不确定我同意"有意义的字符串"建议.不应将枚举视为字符串或数字; 它们是抽象数据类型.如果没有一些辅助方法,就不可能"输出当前值".在Java和.NET中,它是`ToString()`方法.我们JS开发人员已经过于依赖"正常工作"的事情了!此外,应该能够快速"切换"枚举.比较字符串比比较数字慢,所以如果你使用字符串而不是整数,你会得到更糟糕的`switch`性能.
如果有人有兴趣,我[实现](https://github.com/vivin/enumjs)类型安全的枚举类似于他们在Java中的方式.这意味着您可以执行`instanceof`检查.例如`ColorEnum.RED instanceof ColorEnum`(返回`true`).您还可以使用名称"ColorEnum.fromName("RED")来解析实例=== ColorEnum.RED`(返回"true").每个实例也有一个`.name()`和一个`.ordinal()`方法,而enum本身有一个`values()`方法,它返回一个包含所有常量的数组.

3> Stijn de Wit..:

更新:感谢所有人的支持,但我认为下面的答案不再是在Javascript中编写枚举的最佳方式.有关更多详细信息,请参阅我的博文:Javascript中的枚举.


警告名称已经成为可能:

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

或者,你可以制作值对象,这样你就可以吃蛋糕了:

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

在Javascript中,因为它是一种动态语言,甚至可以在以后为集合添加枚举值:

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

请记住,身份检查不需要枚举的字段(本例中的值,名称和代码),只是为了方便起见.此外,size属性的名称本身不需要硬编码,但也可以动态设置.因此,假设您只知道新枚举值的名称,您仍然可以毫无问题地添加它:

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

当然,这意味着不能再做出一些假设(例如,该值表示大小的正确顺序).

请记住,在Javascript中,对象就像地图或哈希表.一组名称 - 值对.您可以在不事先了解它们的情况下循环遍历它们或以其他方式操纵它们.

例如:

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

顺便说一句,如果你对命名空间感兴趣,你可能想看看我的解决方案,为javascript简单但强大的命名空间和依赖管理:包JS


@Johanisma:对于枚举来说,这个用例并不真正有意义,因为对它们的整体想法是你事先知道所有的价值观.但是,没有什么可以阻止您稍后在Javascript中添加额外的值.我将在答案中添加一个例子.
+1使用属性方法链接到您的帖子.优雅的基本声明很简单,如在OP中,在需要时添加了属性功能.

4> Randolpho..:

底线:你不能.

你可以伪造它,但你不会得到类型安全.通常,这是通过创建映射到整数值的字符串值的简单字典来完成的.例如:

var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}

Document.Write("Enumerant: " + DaysEnum.tuesday);

这种方法有问题吗?您可能会意外地重新定义您的枚举,或意外地具有重复的枚举值.例如:

DaysEnum.monday = 4; // whoops, monday is now thursday, too

编辑

Artur Czajka的Object.freeze怎么样?这不会阻止你设置星期一到星期四吗? - Fry Quad

当然,Object.freeze完全可以解决我抱怨的问题.我想提醒大家,当我写上面的内容时,Object.freeze并没有真正存在.

现在....现在它开辟了一些非常有趣的可能性.

编辑2
这是一个非常好的用于创建枚举的库.

http://www.2ality.com/2011/10/enums.html

虽然它可能不适合枚举的每一个有效用途,但它有很长的路要走.


javascript中有类型安全吗?
所以不要将值映射到对象属性.使用getter访问enumerant(存储为"private"对象的属性).一个天真的实现看起来像 - "var daysEnum =(function(){var daysEnum = {monday:1,tuesday:2}; return {get:function(value){return daysEnum [value];}}})() ; daysEnum.get( '星期一'); // 1`
@Randolpho:Artur Czajka的Object.freeze怎么样?这不会阻止你设置星期一到星期四吗?
@Scott Evernden:点了.@kangax:重点是它仍然是一个黑客.枚举根本不存在于Javascript,句点,故事结尾.即使蒂姆西尔维斯特提出的模式仍然不是理想的黑客.
使用文字来填充代码是不可维护的,因此为它创建常量是有意义的.当然Javascript也没有常量.所以基本上这只是编写干净代码的一种方法.它不能强制执行,但Javascript可以不多.您可以重新定义常量或函数,或大多数任何东西.EG:document.getElementById = function(){alert("你搞砸了.Javascript不是类型安全的.");};

5> Andre 'Fi'..:

这就是我们所有人想要的:

function Enum(constantsList) {
    for (var i in constantsList) {
        this[constantsList[i]] = i;
    }
}

现在您可以创建您的枚举:

var YesNo = new Enum(['NO', 'YES']);
var Color = new Enum(['RED', 'GREEN', 'BLUE']);

通过这样做,常量可以通常的方式(YesNo.YES,Color.GREEN)获得,并且它们获得顺序的int值(NO = 0,YES = 1; RED = 0,GREEN = 1,BLUE = 2).

您还可以使用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 */
};


编辑 - 小改进 - 现在使用varargs :(不幸的是它在IE上无法正常工作:S ...应该坚持以前的版本)

function Enum() {
    for (var i in arguments) {
        this[arguments[i]] = i;
    }
}

var YesNo = new Enum('NO', 'YES');
var Color = new Enum('RED', 'GREEN', 'BLUE');



6> Vitalii Fedo..:

在大多数现代浏览器中,有一个符号原始数据类型,可用于创建枚举.它将确保枚举的类型安全性,因为JavaScript保证每个符号值都是唯一的,即Symbol() != Symbol().例如:

const COLOR = Object.freeze({RED: Symbol(), BLUE: Symbol()});

要简化调试,可以向枚举值添加说明:

const COLOR = Object.freeze({RED: Symbol("RED"), BLUE: Symbol("BLUE")});

Plunker演示

在GitHub上,您可以找到一个包装器,它简化了初始化枚举所需的代码:

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


Meh ... enum值通常需要可序列化,而Symbols并不易于序列化和反序列化。

7> Duncan..:

我一直在玩这个,因为我喜欢我的名词.=)

使用Object.defineProperty我认为我提出了一个有点可行的解决方案.

这是一个jsfiddle:http://jsfiddle.net/ZV4A6/

使用此方法..您应该(理论上)能够调用和定义任何对象的枚举值,而不会影响该对象的其他属性.

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

由于该属性,writable:false应该使其类型安全.

所以你应该能够创建一个自定义对象,然后调用Enum()它.分配的值从0开始,每个项目递增.

var EnumColors={};
EnumColors.Enum('RED','BLUE','GREEN','YELLOW');
EnumColors.RED;    // == 0
EnumColors.BLUE;   // == 1
EnumColors.GREEN;  // == 2
EnumColors.YELLOW; // == 3


使其类型安全.
如果你在Enum的末尾添加`return this;`你可以这样做:`var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW');`

8> Jack Giffin..:

大多数人的"首选语法"已在上面列出.但是,存在一个主要的首要问题:性能.上述答案中没有一个在最轻微的情况下非常高效,它们都会使代码大小膨胀到极致.为了实现真正的性能,易于阅读代码,以及通过缩小来实现前所未有的代码大小减少,这是进行枚举的正确方法.

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

// later on

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

此外,此语法允许清晰简洁的类扩展,如下所示.

(长度:2,450字节)

// Precondition: var arr = []; //
arr[INDEX_] = ENUM_;


9> Govind Rai..:

使用Javascript 代理

TLDR:将此类添加到您的实用程序方法中并在整个代码中使用它,它会模仿传统编程语言中的Enum行为,并在您尝试访问不存在的枚举器或添加/更新枚举器时实际抛出错误.无需依赖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);
  }
}

然后通过实例化类来创建枚举:

const roles = new Enum({
  ADMIN: 'Admin',
  USER: 'User',
});

完整说明:

从传统语言中获得的Enums的一个非常有用的功能是,如果您尝试访问不存在的枚举器,它们会爆炸(抛出编译时错误).

除了冻结模拟的枚举结构以防止意外/恶意添加其他值之外,其他任何答案都没有解决Enums的内在特性.

您可能已经意识到,在JavaScript中访问不存在的成员只会返回undefined并且不会破坏您的代码.由于枚举数是预定义的常量(即一周中的几天),因此永远不应该存在未定义枚举数的情况.

不要误会我的意思,JavaScript undefined在访问未定义的属性时返回的行为实际上是语言的一个非常强大的功能,但是当你试图模仿传统的Enum结构时,它不是你想要的功能.

这是Proxy对象闪耀的地方.通过ES6(ES2015)的引入,代理在语言中被标准化.以下是MDN的描述:

Proxy对象用于定义基本操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等).

与Web服务器代理类似,JavaScript代理能够拦截对象上的操作(使用"陷阱",如果您愿意,可以调用它们)并允许您在完成之前执行各种检查,操作和/或操作(或在某些情况下完全停止操作,这正是我们想要做的,如果我们尝试引用一个不存在的枚举器).

这是一个使用Proxy对象来模仿Enums的人为例子.此示例中的枚举器是标准HTTP方法(即"GET","POST"等):

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


10> Rob Hardy..:

这是我所知道的一个旧版本,但它通过TypeScript接口实现的方式是:

var MyEnum;
(function (MyEnum) {
    MyEnum[MyEnum["Foo"] = 0] = "Foo";
    MyEnum[MyEnum["FooBar"] = 2] = "FooBar";
    MyEnum[MyEnum["Bar"] = 1] = "Bar";
})(MyEnum|| (MyEnum= {}));

这使您可以查看MyEnum.Bar哪两个返回1,并MyEnum[1]返回"Bar"而不管声明的顺序如何.


当然,如果你真的使用的是Typescript:`enum MyEnum {Foo,Bar,Foobar}

11> Chris..:

这是我使用的解决方案.

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

你定义你的枚举如下:

var COLORS = defineEnum({
    RED : {
        value : 1,
        string : 'red'
    },
    GREEN : {
        value : 2,
        string : 'green'
    },
    BLUE : {
        value : 3,
        string : 'blue'
    }
});

这就是你访问你的枚举的方式:

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

我通常使用最后两种方法来映射来自消息对象的枚举.

这种方法的一些优点:

容易申报枚举

轻松访问您的枚举

您的枚举可以是复杂类型

如果你经常使用getByValue,Enum类有一些关联缓存

一些缺点:

一些杂乱的内存管理在那里进行,因为我保留了对枚举的引用

仍然没有类型安全



12> Abdennour TO..:

在ES7中,您可以依靠静态属性执行优雅的ENUM:

class ColorEnum  {
    static RED = 0 ;
    static GREEN = 1;
    static BLUE = 2;
}

然后

if (currentColor === ColorEnum.GREEN ) {/*-- coding --*/}

(使用类而不是文字对象)的优点是拥有一个父类,Enum然后所有的Enums都将扩展该类.

 class ColorEnum  extends Enum {/*....*/}


不要那样做.`new ColorEnum()`绝对没有意义.
你能解释一下为什么有一个父类是有利的吗?我觉得我错过了什么!
扩展枚举听起来很疯狂,真的

13> hvdd..:

创建一个对象文字:

const Modes = {
  DRAGGING: 'drag',
  SCALING:  'scale',
  CLICKED:  'click'
};


`const`不会使对象的属性不可变,它只意味着变量`Modes`不能被重新赋值给其他东西.为了使其更完整,请使用[`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)和`const`.

14> Yaroslav..:

如果您使用的骨干,你可以得到全面的枚举功能免费使用(通过ID,名称,自定义成员找到)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()



15> Xeltor..:

你的答案太复杂了

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



16> David Miró..:

我修改了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);
    }
  }

测试:

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



17> Manohar Redd..:

IE8不支持freeze()方法.
来源:http://kangax.github.io/compat-table/es5/,点击"显示过时的浏览器?" 在顶部,并检查IE8和冻结行col交集.

在我目前的游戏项目中,我使用了以下内容,因为很少有客户仍在使用IE8:

var CONST_WILD_TYPES = {
    REGULAR: 'REGULAR',
    EXPANDING: 'EXPANDING',
    STICKY: 'STICKY',
    SHIFTING: 'SHIFTING'
};

我们也可以这样做:

var CONST_WILD_TYPES = {
    REGULAR: 'RE',
    EXPANDING: 'EX',
    STICKY: 'ST',
    SHIFTING: 'SH'
};

甚至这个:

var CONST_WILD_TYPES = {
    REGULAR: '1',
    EXPANDING: '2',
    STICKY: '3',
    SHIFTING: '4'
};

最后一个,对于字符串来说似乎最有效,如果您有服务器和客户端交换这些数据,它会减少您的总带宽.
当然,现在你有责任确保数据中没有冲突(RE,EX等必须是唯一的,1,2等也应该是唯一的).请注意,为了向后兼容,您需要永久维护这些内容.

分配:

var wildType = CONST_WILD_TYPES.REGULAR;

比较:

if (wildType === CONST_WILD_TYPES.REGULAR) {
    // do something here
}



18> Vivin Paliat..:

我想出了这种方法,该方法以Java枚举为模型。这些是类型安全的,因此您也可以执行instanceof检查。

您可以这样定义枚举:

var Days = Enum.define("Days", ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]);

Days现在指的是Days枚举:

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"

实现:

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
    }

})();

推荐阅读
落单鸟人
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有