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


2081

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

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

// later on

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

还是有更好的成语?


131
不要0用作枚举数。除非用于未设置的内容。false || undefined || null || 0 || "" || '' || NaN使用进行比较时,JS将所有值视为相同的值==
matsko 2015年

152
@matsko不仅仅是反对使用==的说法吗?
sdm350

6
0 == null返回false
mcont 2015年

11
但是false == 0+null == 0(而且)(有时会在您不期望的时候发生数字转换)null == undefined,而且+undefined也是NaN(尽管NaN != NaN)。
sanderd17

46
双重平等矩阵比Microsoft Word的自动格式设置更令人困惑
-aaaaaa

Answers:


894

从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

确保类型安全性(使用枚举或其他方式)的程度更高的一种方法是使用诸如TypeScriptFlow之类的工具。

资源

不需要引号,但为了保持一致性,我保留了它们。


6
根据Wikipedia(en.wikipedia.org/wiki/JavaScript#Versions)的说法,它适用于Firefox 4,IE 9,Opera 11.60,并且我知道它可以在Chrome中运行。
Artur Czajka,2012年

77
这是2012年的正确答案。更简单:var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });。您无需指定ID,只需使用一个空对象比较枚举即可。if (incommingEnum === DaysEnum.monday) //incommingEnum is monday
加布里埃尔·拉马斯

34
为了向后兼容,if (Object.freeze) { Object.freeze(DaysEnum); }
2012年

16
我想指出的是,这样做({ monday: {}, 等等意味着,如果您通过字符串化将该对象转换为JSON,将会得到[{"day": {}}]不起作用。
jcollum

9
@Supuhstar现在我对这个问题的看法有所不同。不要使用freeze(),它完全没有用,并且浪费时间做“愚蠢”的事情。如果要公开枚举,只需公开以下内容:var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}。像我之前的评论中那样比较对象比比较数字要慢得多。
Gabriel Llamas 2014年

608

这并不是一个很好的答案,但是我个人认为这很好

话虽如此,由于值的大小无关紧要(您使用过0、1、2),因此,如果您想输出当前值,我将使用有意义的字符串。


377
这是在另一个答案中说明的,但是由于该答案是公认的答案,因此我将其发布在此处。OP的解决方案是正确的。如果与结合使用,效果会更好Object.freeze()。这将防止其他代码更改枚举的值。示例:var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2});
Sildoreth 2014年

5
@TolgaE谢谢您的图书馆!它启发了我不仅将其煮到最低限度,而且还增加了一些功能!我已经分叉了所有内容,并将其全部放在此处:github.com/BlueHuskyStudios/Micro-JS-Enum
Supuhstar 2014年

3
@Supuhstar太好了!我很高兴您可以使用它。如果您希望将它合并到此库中,请随时发出请求,然后我可以更新npm库
Tolga E

2
如果有人感兴趣,我已经实现了类似于Java的类型安全枚举。这意味着您可以进行instanceof检查。例如ColorEnum.RED instanceof ColorEnum(返回true)。您也可以使用名称解析实例ColorEnum.fromName("RED") === ColorEnum.RED(返回true)。每个实例还具有.name().ordinal()方法,而枚举本身具有values()返回所有常量数组的方法。
Vivin Paliath

3
我不确定我是否同意“有意义的字符串”建议。枚举不应被视为字符串或数字;它们是抽象数据类型。没有一些辅助方法就不可能“输出当前值”。在Java和.NET中,其ToString()方法。我们的JS开发人员已经太依赖“正常工作”了!同样,一个人应该能够快速地switch列举出一个枚举。比较字符串要比比较数字慢,因此switch如果使用字符串而不是整数,则性能会稍差一些。
Rabadash8820'7

501

更新

感谢大家的支持,但是我认为下面的答案不再是用JavaScript编写枚举的最佳方法。有关更多详细信息,请参见我的博客文章:JavaScript中的Enums


警报名称已经可以:

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

另外,您可以使values对象成为对象,因此您也可以吃蛋糕:

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中,对象就像maphash table一样。一组名称/值对。您可以循环浏览它们或以其他方式操纵它们,而无需事先了解它们。

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的简单但功能强大的名称空间和依赖项管理的解决方案:Packages JS


那么,如果您仅拥有SIZE的名称,该如何创建它呢?
Johanisma 2011年

2
@Johanisma:用例对于枚举并没有真正意义,因为它们的整体思想是您事先了解所有值。但是,没有什么可以阻止您稍后在Javascript中添加额外的值。我将在答案中添加一个示例。
Stijn de Witt

2
使用属性方法为您的信息链接+1。优雅,因为基本声明很简单(如在OP中一样),并在需要时添加了特性。
goodeye 2014年

@Stijin,真的很喜欢您更新的解决方案。在您的博客评论中以及以下评论中张贴代码。基本上,使用函数从现有哈希列表执行属性构建,并有选择地冻结它(我的列表中为mkenum_2)。干杯。
Andrew Philips

还有一个实现它的库,还包括比较和反向搜索的出色
Roman M

83

底线:不能。

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

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

尽管它可能不适合枚举的所有有效用法,但它却走了很长一段路。


103
在javascript中有类型安全性吗?
斯科特·埃弗登(Evernden),2009年

3
因此,请勿将值映射到对象属性。使用getter访问枚举数(存储为“私有”对象的属性)。一个幼稚的实现看起来像var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1
kangax

2
@斯科特·埃弗登(Scott Evernden):要点。@kangax:关键是它仍然是一个hack。枚举根本不存在于Javascript,句号,故事结尾中。即使是蒂姆·西尔维斯特(Tim Sylvester)提出的模式,也仍然不尽人意。
Randolpho

2
在代码中撒上文字不是很容易维护,因此为它创建常量是有意义的。当然,Javascript也没有常量。因此,基本上,这只是编写干净代码的一种方法。它不能被强制执行,但是Javascript中不能执行。您可以重新定义常量或函数,或几乎任何东西。EG:document.getElementById = function(){alert(“您被搞砸了。Javascript不是类型安全的。”);};
Stijn de Witt

3
@Randolpho:Artur Czajka的Object.freeze怎么样?这样是否可以防止您将星期一设置为星期四?
Michael-Clay Shirky在哪里,2012年

56

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

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

通过这样做,可以按通常的方式访问常量(是,是,Color.GREEN),并且它们将获得一个连续的整数值(否= 0,是= 1;红色= 0,绿色= 1,蓝色= 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');

喜欢这个答案的简单性!
Marquizzo

@Marquizzo(和OP)我根据以下答案创建了改进的版本:stackoverflow.com/a/60309416/1599699
Andrew

53

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

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

为了简化调试,可以在枚举值中添加描述:

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

柱塞演示

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

从理论上讲,这是正确的答案。实际上,2015年浏览器支持还远远不够。到目前为止尚未做好生产准备。
vbraun

1
尽管还没有浏览器支持,但这是最好的答案,因为它已经接近Symbol预期的用途。
rvighne '16

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

3
是我还是Object.freeze只为那些尚未接受“风险自担猴子补丁”是JS的社会契约的人?
安迪

@Andy是的,序列化很烦人。最后我做一个明确toJSON的含类使用这种方式:stackoverflow.com/questions/58499828/...
西罗桑蒂利冠状病毒审查六四事件法轮功

30

𝗣𝗹𝗮𝗶𝗻𝗩𝗮𝗻𝗶𝗹𝗹𝗮𝗝𝗦𝗩𝗮𝗿𝗶𝗮𝗯𝗹𝗲𝗡𝗮𝗺𝗲𝘀

让我们直接解决问题:文件大小。此处列出的所有其他答案都会使您的代码膨胀到极致。我向您介绍,为了获得最佳性能,代码的可读性,大规模项目管理,许多代码编辑器中的语法提示以及通过最小化来减小代码大小,这是进行枚举的正确方法:下划线表示变量。


wvwvwvwvwvwvwvwvwvwvwvwvww

下划线符号变量

如上图和以下示例所示,这是五个简单的入门步骤:

  1. 确定枚举组的名称。想一想可以描述枚举目的或至少枚举条目的名词。例如,代表用户可以选择的颜色的一组枚举可能比COLORS更好地命名为COLORCHOICES。
  2. 确定组中的枚举是互斥的还是独立的。如果是互斥的,则以开头的每个枚举变量名ENUM_。如果是独立的或并排使用,请使用INDEX_
  3. 对于每个条目,创建一个新的局部变量,其名称以ENUM_或开头INDEX_,然后是组的名称,然后是下划线,然后是属性的唯一友好名称。
  4. 添加ENUMLENGTH_ENUMLEN_INDEXLENGTH_,或者INDEXLEN_(无论是LEN_LENGTH_在最后是个人喜好)枚举变量。您应该在代码中尽可能使用此变量,以确保向枚举添加额外的条目并递增此值不会破坏您的代码。
  5. 给每一个连续变量枚举值一个超过最后,从0开始。有迹象表明,说这个页面的评论0不应该被用来作为一个枚举值,因为0 == null0 == false0 == "",和其他JS疯狂。我谨向您指出,为避免此问题并同时提高性能,请始终使用===并且不要让==代码出现在typeof(ex typeof X == "string")之外,除非带有(ex )。在使用的所有年份中===,我从来没有遇到过使用0作为枚举值的问题。如果您仍然很笨拙,那么在许多情况下1可以用作ENUM_枚举(而不是INDEX_枚举)的起始值,而不会降低性能。
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
}

这是我记得何时使用INDEX_和何时使用的方法ENUM_

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

但是,ENUM_在某些情况下,例如当计算每个项目的出现次数时,它可能适合作为索引。

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

观察一下,在上面的代码中,添加一种新的宠物真的很容易:您只需要在其后追加一个新条目ENUM_PET_RAT并进行相应的更新即可ENUMLEN_PET。在其他枚举系统中添加新条目可能会更加困难且容易出错。


wvwwww wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvw

𝗘𝘅𝘁𝗲𝗻𝗱𝗨𝗽𝗽𝗲𝗿𝗰𝗮𝘀𝗲𝗩𝗮𝗿𝗶𝗮𝗯𝗹𝗲𝘀𝗪𝗶𝘁𝗵𝗔𝗱𝗱𝗶𝘁𝗶𝗼𝗻

此外,此枚举语法允许清晰简洁的类扩展,如下所示。要扩展类,请LEN_在父类的条目中添加一个递增的数字。然后,用自己的LEN_条目完成子类,以便将来可以进一步扩展子类。

附加扩展图

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

(长度:2,450字节)

有人可能会说这比其他解决方案不太实用:它占用了很多空间,需要花费很长时间来编写,并且没有使用糖语法。如果他们不缩减代码,那么这些人将是正确的。但是,没有理性的人会在最终产品中留下未缩小的代码。对于这种缩小,Closure Compiler是我尚未找到的最好的。在线访问可以在这里找到。Closure编译器能够获取所有这些枚举数据并将其内联,从而使您的Javascript变得超级精简,并且能够快速运行超级精简。因此,使用Closure Compiler进行压缩。观察一下。


wvwwww wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvw

𝗪𝗶𝘁𝗵𝗠𝗶𝗻𝗶𝗳𝘆 𝗖𝗹𝗼𝘀𝘂𝗿𝗲𝗖𝗼𝗺𝗽𝗶𝗹𝗲𝗿

Closure编译器能够通过推理执行一些非常令人难以置信的优化,而这些推理远远超出了其他Javascript缩小器的能力。Closure Compiler能够内联设置为固定值的基本变量。Closure Compiler还能够基于这些内联值进行推断,并消除if语句和循环中未使用的块。

通过Closure编译器拧代码

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

(长度:605字节)

Closure Compiler奖励您更聪明地编码和组织代码,因为尽管许多压缩工具会以较小的文件大小来惩罚有组织的代码,但是Closure Compiler能够筛选出所有的整洁度,如果您使用技巧也能输出较小的文件大小像变量名枚举一样。在这种情况下,这就是编码的圣杯:一种既可以以较小的最小尺寸协助您的代码,又可以通过训练更好的编程习惯来协助您的思想的工具。


wvwwww wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvw

𝗦𝗶𝘇𝗲

现在,让我们看看没有任何这些枚举的等效文件将有多大。

不使用枚举的源(长度:1,973字节(比枚举代码短477字节!))
最小尺寸,不使用枚举(长度:843字节(比枚举代码长 238字节))

代码大小表



可以看出,没有枚举,源代码会更短,但代价是较大的缩小代码。我对你一无所知; 但是我确定我不会将源代码包含到最终产品中。因此,这种枚举形式非常优越,因为它导致较小的缩小文件大小。


wvwwww wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvw

𝗖𝗼𝗼𝗽𝗲𝗿𝗮𝘁𝗶𝘃𝗲🤝𝗕𝘂𝗴𝗙𝗶𝘅𝗶𝗻𝗴

这种枚举形式的另一个优点是,它可用于轻松管理大型项目,而无需牺牲最小化的代码大小。在与许多其他人一起从事大型项目时,最好使用创建代码的人明确标记和标记变量名称,这样可以快速识别出代码的原始创建者以进行协作的错误修复,这可能是有益的。

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

𝗣𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲

此外,这种枚举形式在最小化后也要快得多。在普通的命名属性中,浏览器必须使用哈希图来查找该属性在对象上的位置。尽管JIT编译器在对象上智能地缓存了该位置,但是由于特殊情况(例如从对象中删除较低的属性),仍然存在巨大的开销。

但是,使用连续的非稀疏整数索引的PACKED_ELEMENTS数组,由于已经指定了内部数组中值的索引,因此浏览器可以跳过很多开销。是的,根据ECMAScript标准,所有属性都应视为字符串。尽管如此,ECMAScript标准的这一方面在性能上还是极具误导性,因为所有浏览器都对数组中的数字索引进行了特殊的优化。

/// Hashmaps are slow, even with JIT juice
var ref = {};
ref.count = 10;
ref.value = "foobar";

将上面的代码与下面的代码进行比较。

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

一个人可能会反对带有枚举的代码,而枚举似乎要比带有普通对象的代码长得多,但看起来可能是骗人的。重要的是要记住,使用史诗级的Closure Compiler时,源代码的大小与输出的大小不成比例。观察一下。

/// Hashmaps are slow, even with JIT juice
var a={count:10,value:"foobar"};

没有枚举的最小化代码在上面,而带有枚举的最小化代码在下面。

/// Arrays, however, are always lightning fast
var a=[10,"foobar"];

上面的示例说明,除了具有卓越的性能之外,枚举的代码还导致较小的缩小文件大小。


wvwwww wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvw

𝗗𝗲𝗯𝘂𝗴𝗴𝗶𝗻𝗴

此外,这种一个人的个人在上面的樱桃是用这种形式与沿枚举CodeMirror在Javascript模式文本编辑器。CodeMirror的Javascript语法突出显示模式突出显示当前作用域中的局部变量。这样,您就可以正确输入变量名,从而立即知道,因为如果以前使用var关键字声明了变量名,则变量名会变成特殊的颜色(默认为青色)。即使您不使用CodeMirror,也至少浏览器会抛出一个有用的信息。[variable name] is not defined使用错误的枚举名称执行代码时发生异常。同样,JavaScript工具(例如JSLint和Closure Compiler)在告诉您何时键入枚举变量名称时非常吵闹。CodeMirror,浏览器和各种Javascript工具一起使调试这种枚举形式变得非常简单而且非常容易。

CodeMirror高亮演示

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
}

在以上代码段中,由于ENUM_COLORENUM_DNE不存在错误,您收到了警报。


wvwwww wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvw

☑☑

我认为可以肯定地说,这种枚举方法确实是最好的方法,不仅是为了减小代码大小,而且还是性能,调试和协作。

阅读了有用的问题后,我感谢作者通过在问题框中单击左上角的向上箭头,将时间花在写作上。每个答案框也具有这些向上箭头之一。


嗯 与代码大小相比,我强烈更喜欢可读性,易用性和理解性。
安德鲁

1
@Andrew有了我的回答,您可以同时拥有。我的回答结果最容易使用/管理代码,并在最小缩小的代码size.🙂
杰克·吉芬

1
@Andrew我尝试将我的另一个枚举(YEA!)应用于我的答案中的颜色解析器示例。但是,我发现了您可能要解决的几个问题。YEA无法扩展子类的枚举,这迫使我创建单独的父类和子类,这在大型项目中可能很难管理。YEA不能确保该条目存在(例如colors.REEDyield undefined),因此错别字会产生难以捉摸的难题。YEA不能区分使用枚举作为索引还是ID,从而导致代码看起来看起来一样混乱。…
杰克·吉芬

1
@Andrew…YEA妨碍了Closure Compiler的最小化能力。将具有YEA(3549字节)的源代码与具有YEA(1344字节)的最小代码与具有我的解决方案(604字节)的最小代码进行比较。最后,YEA涉及“按名称映射”,因为它将字符串名称与枚举ID分开。我的只考虑ID,因此不需要“按名称映射”,从而简化了设计并提高了性能。感谢您分享您的解决方案,但在实用之前需要进行很多修复。
杰克·吉芬

1
@Andrew您有权发表自己的看法,因为我是我的as
Jack Giffin

23

我一直在玩这个游戏,因为我喜欢我的枚举。=)

使用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

3
如果您return this;在Enum末尾添加,则可以执行以下操作:var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW');
HBP 2013年

我没有考虑,因为这不是我通常的做事方法。但是你是绝对正确的!我会编辑英寸
邓肯

我真的很喜欢这样,尽管我不喜欢破坏对象空间(使用全局函数ENUM)。将此转换为mkenum函数,并添加了可选的数字分配=> var mixedUp = mkenum('BLACK',{RED:0x0F00,BLUE:0X0F,GREEN:0x0F0,WHITE:0x0FFF,ONE:1},二,三,四) ; //在下面添加我的代码作为答案。谢谢。
Andrew Philips

老实说,我什至不再使用它。我一直在使用Google的Closure Compiler,如果您使用“高级”设置,它的效果可能不太好(或者只会使事情复杂化)。因此,我刚刚回到标准对象表示法。
邓肯

1
false是默认writableenumerableconfigurable。无需仔细检查默认值。
2015年

23

使用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',
});

完整说明:

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

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

您可能已经知道,访问JavaScript中不存在的成员只会返回undefined并且不会炸毁您的代码。由于枚举数是预定义的常量(即一周中的几天),因此永远都不会出现未定义枚举数的情况。

别误会,JavaScript undefined在访问未定义属性时返回的行为实际上是语言的强大功能,但是当您尝试模拟传统Enum结构时,这并不是您想要的功能。

这是代理对象发光的地方。通过引入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"


ASIDE:代理到底是什么?

我记得当我第一次开始到处都看到“代理”一词时,很长一段时间对我来说绝对没有意义。如果现在就是您,我认为将代理普遍化的一种简便方法是将它们视为软件,机构,甚至是充当两个服务器,公司或人员之间的中间人或中间人的人员。


如何做像myEnum.valueOf(“ someStringValue”)一样的事情?预期:如果输入字符串的值是枚举数的元素,则应返回该项目。如果没有项目具有该字符串值,则引发异常。
sscarduzio

@sscarduzio您可以通过将默认valueOf方法指定为Enum类的实例方法来覆盖默认方法。但是,为什么要以这种方式访问​​它而不是仅通过点符号访问它?
Govind Rai'1

我的枚举是const logLevelEnum = new Enum({INFO:“ info”,DEBUG:“ debug”}),我从输入中解析了任意字符串“ info”或“ debug”。所以我需要类似currentLogLevel = logLevelEnum.parseOrThrow(settings.get(“ log_level”))
sscarduzio

1
你为什么不做logLevelEnum[settings.get("log_level")]呢?添加parseOrThrow只会重复代理陷阱已为​​您完成的工作。
Govind Rai

17

我知道这是一个古老的方法,但是此后通过TypeScript接口实现的方法是:

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

这样MyEnum.Bar一来,MyEnum[1]无论声明的顺序如何,都可以查找返回1以及返回“ Bar”的这两者。


1
再加上MyEnum [“ Bar”]的工作将返回1 ...到目前为止<3 TypeScript ...
DavidKarlaš2014年

3
当然,如果您实际上正在使用打字稿:enum MyEnum { Foo, Bar, Foobar }
议会

16

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

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

然后

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

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

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

4
您能解释一下为什么有家长课是一种优势吗?我觉得我想念什么!
Jon G

7
不要那样做 new ColorEnum()完全没有道理。
Bergi

3
扩展枚举听起来很疯狂,真的
Codii

一旦语言本身不支持它,就必须遵守该约定并像这样使用!我同意!
xpto

我认为(OP)正在使用OP:纯静态的好处在于,它可以作为一个单例在任何地方使用,并且您无需实例化该类-OP并不建议您这样做!我觉得他说的是,超Enum有标准的静态它枚举的方法,如getValues()getNames()iterate(),等。如果是那样的话,你不必重新实现他们为每一个新种enum
工程师

15

这是我使用的解决方案。

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类具有一些关联缓存

一些缺点:

  • 在这里进行一些混乱的内存管理,因为我保留了对枚举的引用
  • 仍然没有类型安全

14

创建一个对象文字:

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

12
const不会使对象的属性不变,这仅意味着不能将变量Modes重新分配给其他对象。要使其更完整,请Object.freeze()在旁边使用const
rvighne '16

请不要使用Object.freeze。它防止Closure Compiler内联对象。
杰克·吉芬

11

如果您使用的骨干,你可以得到全面的枚举功能免费使用(通过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()

8

你的答案太复杂了

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

1
@JackGiffin我同意您的回答更有效,我的可能会占用更多的内存,尽管您不应该假定每个人都希望C ++实现它的方式枚举。请尊重其他答案,开发人员可能更喜欢此答案。
Xeltor

7

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

6

我想出了这种方法,方法以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
    }

})();

看起来不错,也许您应该检查该freeze方法是否存在向后兼容?例如,if (Object.freeze) { Object.freeze(values); }
FBB

好点子!会做!
Vivin Paliath 2015年

6

IE8不支持Frozen()方法。
来源: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
}

5
var ColorEnum = {
    red: {},
    green: {},
    blue: {}
}

您无需确保不会以这种方式将重复的数字分配给不同的枚举值。实例化一个新对象并将其分配给所有枚举值。


这个答案被低估了。它的简单性是我最喜欢的想法之一。在实践中,我认为我会坚持使用字符串,因为现在更容易调试。
Domino

嗯,只要确保不会两次调用此代码即可
Andrew

4

这是实现TypeScript枚举的几种不同方法。

最简单的方法是仅迭代一个对象,向该对象添加反转的键值对。唯一的缺点是您必须手动设置每个成员的值。

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


这是一个lodash mixin,用于使用字符串创建枚举。尽管此版本涉及更多点,但它会自动为您编号。本示例中使用的所有lodash方法都具有常规的JavaScript等效项,因此您可以根据需要轻松地将它们切换出来。

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

非常聪明,谢谢
Ilan

4

我刚刚发布了一个NPM包gen_enum,您可以使用Javascript快速创建Enum数据结构:

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 

关于这个小工具的一件好事是在现代环境(包括nodejs和IE 9+浏览器)中,返回的Enum对象是不可变的。

有关更多信息,请查看https://github.com/greenlaw110/enumjs

更新

我已经过时了gen_enum,并将功能合并到constjs程序包中,该程序包提供了更多功能,包括不可变对象,JSON字符串反序列化,字符串常量和位图生成等。请检出https://www.npmjs.com/package/constjs有关更多信息

从升级gen_enumconstjs仅更改语句

var genEnum = require('gen_enum');

var genEnum = require('constjs').enum;

4

最简单的解决方案:

创建

var Status = Object.freeze({
    "Connecting":0,
    "Ready":1,
    "Loading":2,
    "Processing": 3
});

获得价值

console.log(Status.Ready) // 1

取得金钥

console.log(Object.keys(Status)[Status.Ready]) // Ready

4

我制作了一个Enum类,可以在O(1)处获取值和名称。它还可以生成包含所有名称和值的对象数组。

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

您可以这样初始化它:

var enum1 = new Enum({
    item1: 0,
    item2: 1,
    item3: 2
});

要获取值(如C#中的Enums):

var val2 = enum1.item2;

要获取值的名称(将相同的值用于不同的名称时可能会模棱两可):

var name1 = enum1.GetName(0);  // "item1"

要获取具有对象中每个名称和值的数组:

var arr = enum1.GetObjArr();

将产生:

[{ Name: "item1", Value: 0}, { ... }, ... ]

您还可以轻松获取html select选项:

var html = enum1.GetSelectOptionsHTML();

其中:

"<option value='0'>item1</option>..."

4

尽管只有静态方法(而不是静态的属性)在ES2015(见支持这里为好,§15.2.2.2),奇怪的是您可以用使用下面巴贝尔的es2015预设:

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

我发现即使在各个模块之间(例如CellState从另一个模块导入枚举),以及在使用Webpack导入模块时,它也都可以按预期工作。

与大多数其他答案相比,此方法的优势在于您可以将其与静态类型检查器(例如Flow一起使用,并且可以在开发时使用静态类型检查断言您的变量,参数等具有特定的CellState“枚举”,而不是其他枚举(如果使用通用对象或符号,则无法区分)。

更新

上面的代码有一个缺陷,就是它允许人们创建其他类型的对象CellState(即使CellState由于冻结而无法将它们分配给的静态字段)。尽管如此,以下更完善的代码仍具有以下优点:

  1. 不能CellState再创建类型的对象
  2. 您可以确保没有为两个枚举实例分配相同的代码
  3. 从字符串表示形式获取枚举的实用方法
  4. values返回枚举的所有实例的函数不必以上述手动(且容易出错)的方式创建返回值。

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

很好的例子:-)
Ashraf.Shk786

4

es7方式,(迭代器,冻结),用法:

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'

码:

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(', ') }

}

4

这是Typescript将其enum转换为Javascript的方式:

var makeEnum = function(obj) {
    obj[ obj['Active'] = 1 ] = 'Active';
    obj[ obj['Closed'] = 2 ] = 'Closed';
    obj[ obj['Deleted'] = 3 ] = 'Deleted';
}

现在:

makeEnum( NewObj = {} )
// => {1: "Active", 2: "Closed", 3: "Deleted", Active: 1, Closed: 2, Deleted: 3}

起初我很困惑为什么要obj[1]返回'Active',但后来意识到它的死法很简单- 赋值运算符先赋值然后返回:

obj['foo'] = 1
// => 1

4

你可以做这样的事情

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

如该库中所定义。 https://github.com/webmodule/foo/blob/master/foo.js#L217

完整示例 https://gist.github.com/lnt/bb13a2fd63cdb8bce85fd62965a20026


3

一种快速简单的方法是:

var Colors = function(){
return {
    'WHITE':0,
    'BLACK':1,
    'RED':2,
    'GREEN':3
    }
}();

console.log(Colors.WHITE)  //this prints out "0"

6
该功能是不必要的,可以为您提供与OP发布的结果完全相同的结果。
Sildoreth 2014年

3

截至撰写本文时(201410月) -这是一个当代的解决方案。我正在将解决方案编写为节点模块,并且包括使用Mocha和Chai以及underscoreJS进行的测试。您可以轻松地忽略它们,如果需要的话,可以使用Enum代码。

看过很多文章,它们的库太复杂了。获得Java枚举支持的解决方案非常简单,实际上并不需要。这是代码:

档案:enums.js

_ = require('underscore');

var _Enum = function () {

   var keys = _.map(arguments, function (value) {
      return value;
   });
   var self = {
      keys: keys
   };
   for (var i = 0; i < arguments.length; i++) {
      self[keys[i]] = i;
   }
   return self;
};

var fileFormatEnum = Object.freeze(_Enum('CSV', 'TSV'));
var encodingEnum = Object.freeze(_Enum('UTF8', 'SHIFT_JIS'));

exports.fileFormatEnum = fileFormatEnum;
exports.encodingEnum = encodingEnum;

并进行测试以说明它能为您提供什么:

文件:enumsSpec.js

var chai = require("chai"),
    assert = chai.assert,
    expect = chai.expect,
    should = chai.should(),
    enums = require('./enums'),
    _ = require('underscore');


describe('enums', function () {

    describe('fileFormatEnum', function () {
        it('should return expected fileFormat enum declarations', function () {
            var fileFormatEnum = enums.fileFormatEnum;
            should.exist(fileFormatEnum);
            assert('{"keys":["CSV","TSV"],"CSV":0,"TSV":1}' === JSON.stringify(fileFormatEnum), 'Unexpected format');
            assert('["CSV","TSV"]' === JSON.stringify(fileFormatEnum.keys), 'Unexpected keys format');
        });
    });

    describe('encodingEnum', function () {
        it('should return expected encoding enum declarations', function () {
            var encodingEnum = enums.encodingEnum;
            should.exist(encodingEnum);
            assert('{"keys":["UTF8","SHIFT_JIS"],"UTF8":0,"SHIFT_JIS":1}' === JSON.stringify(encodingEnum), 'Unexpected format');
            assert('["UTF8","SHIFT_JIS"]' === JSON.stringify(encodingEnum.keys), 'Unexpected keys format');
        });
    });

});

如您所见,您将获得一个Enum工厂,只需调用enum.keys就可以获取所有键,并且可以将这些键本身与整数常量进行匹配。您可以使用不同的值重用工厂,并使用Node的模块化方法导出生成的Enum。

再说一次,如果您只是一个临时用户,或者在浏览器中,只需将代码的工厂部分包括在内,如果您不希望在代码中使用下划线库,则也有可能删除下划线库。


5
您是否可以仅用“只是想要枚举而不是工厂,下划线或任何花哨的临时用户的方式来发布答案”?
GreenAsJade

5
即使从开发人员的角度来看这非常棒,但它也不是很干净或可读性强。OP中的Enum解决方案在各个方面都更加容易且易于阅读,因此更好地使用。尽管如此,您还是想到了这一点。
大卫

3

我认为它很容易使用。https://stackoverflow.com/a/32245370/4365315

var A = {a:11, b:22}, 
enumA = new TypeHelper(A);

if(enumA.Value === A.b || enumA.Key === "a"){ 
... 
}

var keys = enumA.getAsList();//[object, object]

//set
enumA.setType(22, false);//setType(val, isKey)

enumA.setType("a", true);

enumA.setTypeByIndex(1);

更新:

有我的助手代码(TypeHelper)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.