ChatGPT解决这个技术问题 Extra ChatGPT

如何保证我的枚举定义在 JavaScript 中不会改变?

以下是否会使对象满足枚举在 JavaScript 中的所有特征?就像是:

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

// later on

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

还是有其他方法可以做到这一点?

不要使用 0 作为枚举数。除非它用于尚未设置的东西。当使用 == 进行比较时,JS 将 false || undefined || null || 0 || "" || '' || NaN 视为相同的值。
@matsko 不只是反对使用 == 的论据吗?
0 == null 返回错误
但是 false == 0+null == 0(有时会在您意想不到的情况下转换为数字),而 null == undefined 也是如此,并且 +undefinedNaN(尽管是 NaN != NaN)。
双等式矩阵比 microsoft word 的自动格式化更令人困惑

L
LegionMammal978

从 1.8.5 开始可以使用 seal and freeze the object,因此将上述定义为:

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 这样的工具。

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


根据 Wikipedia (en.wikipedia.org/wiki/JavaScript#Versions),它适用于 Firefox 4、IE 9、Opera 11.60,我知道它适用于 Chrome。
这是 2012 年现在的正确答案。更简单:var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });。您无需指定 id,只需使用空对象来比较枚举即可。 if (incommingEnum === DaysEnum.monday) //incommingEnum is monday
为了向后兼容,if (Object.freeze) { Object.freeze(DaysEnum); }
我想指出,做 ({ monday: {}, 等意味着如果你通过 stringify 将该对象转换为 JSON,你将得到 [{"day": {}}] ,这是行不通的。
@Supuhstar我现在对这个问题的看法不同了。不要使用 freeze(),它完全没用,而且浪费时间做“愚蠢”的事情。如果你想公开一个枚举,只需公开这个:var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}。在我之前的评论中比较对象比比较数字要慢得多。
G
Gareth

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

话虽如此,由于值是什么并不重要(您使用了 0、1、2),我会使用有意义的字符串以防您想要输出当前值。


这在另一个答案中有所说明,但由于此答案是公认的答案,因此我将在此处发布。 OP的解决方案是正确的。但是,如果与 Object.freeze() 一起使用,效果会更好。这将防止其他代码更改枚举的值。示例:var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2});
@TolgaE 谢谢你的图书馆!它启发了我不仅将其归结为最低限度,而且还添加了一些功能!我已经把你的分叉了,把它都放在这里了:github.com/BlueHuskyStudios/Micro-JS-Enum
@Supuhstar 太好了!我很高兴你可以使用它。如果你想将它合并到这个库中,请随时提出拉取请求,然后我可以更新 npm 库
如果有人感兴趣,我有 implemented 类型安全枚举,类似于它们在 Java 中的方式。这意味着您可以进行 instanceof 检查。例如 ColorEnum.RED instanceof ColorEnum(返回 true)。您还可以使用名称 ColorEnum.fromName("RED") === ColorEnum.RED 解析实例(返回 true)。每个实例也有一个 .name() 和一个 .ordinal() 方法,而枚举本身有一个 values() 方法,它返回一个包含所有常量的数组。
我不确定我是否同意“有意义的字符串”的建议。不应将枚举视为字符串或数字;它们是抽象数据类型。如果没有一些辅助方法,应该不可能“输出当前值”。在 Java 和 .NET 中,它是 ToString() 方法。我们 JS 开发人员已经太依赖“正常工作”的东西了!此外,一个人应该能够在枚举上快速switch。比较字符串比比较数字慢,因此如果使用字符串而不是整数,switch 的性能会稍差。
M
Muhammad Dyas Yaskur

更新

我认为我下面的答案不再是用 JavaScript 编写枚举的最佳方式。有关详细信息,请参阅我的博文:Enums in 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);
   }
}

或者,您可以创建 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 中,对象就像地图或哈希表。一组名称-值对。您可以循环浏览它们或以其他方式操纵它们,而无需提前了解它们。

例子

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:该用例对枚举没有真正意义,因为它们的整个想法是您提前知道所有值。但是,没有什么能阻止您稍后在 Javascript 中添加额外的值。我将在我的答案中添加一个示例。
使用属性方法为您的帖子的链接+1。优雅之处在于基本声明很简单,就像在 OP 中一样,在需要时添加了属性功能。
@Stijin,真的很喜欢您更新的解决方案。在您的博客上的评论中发布代码,并作为下面的评论。基本上,使用一个函数,从现有的哈希列表执行属性构建,并可选择冻结它(我的列表中的 mkenum_2)。干杯。
还有一个实现它的库,还包括比较和反向搜索的不错功能:github.com/adrai/enum
R
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 怎么样?这不会阻止您将星期一设置为星期四吗? - 炒四

当然,Object.freeze 会完全解决我抱怨的问题。我想提醒大家,当我写上述内容时,Object.freeze 并不存在。

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

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

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

虽然它可能不适合枚举的所有有效用途,但它还有很长的路要走。


javascript中有类型安全吗?
所以不要将值映射到对象属性。使用 getter 访问枚举数(存储为“私有”对象的属性)。一个简单的实现看起来像 - var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1
@Scott Evernden:要点。 @kangax:关键是它仍然是一个黑客。枚举根本不存在于 Javascript、句号、故事结尾。即使是 Tim Sylvester 建议的模式,仍然不是理想的 hack。
用文字洒在代码上不是很容易维护,因此为它创建常量是有意义的。当然 Javascript 也没有常量。所以基本上这只是一种编写干净代码的方法。它不能被强制执行,但在 Javascript 中却不多。您可以重新定义常量或函数,或者大部分内容。 EG:document.getElementById = function() {alert("你搞砸了。Javascript 不是类型安全的。");};
@Randolpho:Artur Czajka 的 Object.freeze 怎么样?这不会阻止您将星期一设置为星期四吗?
A
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 */
};

编辑 - 小改进 - 现在使用可变参数:(不幸的是,它在 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(和 OP)我根据这个答案创建了一个改进版本:stackoverflow.com/a/60309416/1599699
@Andrew 我创建了一个单独的、经过深思熟虑、经过仔细考虑和彻底审查的答案,我在生产中多次使用过该答案:stackoverflow.com/a/50355530/5601591
Y
YakovL

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

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

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

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

Plunker demo

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 浏览器支持还远远不够。到目前为止还没有准备好生产。
尽管尚不支持浏览器,但这是最佳答案,因为这接近 Symbol 的用途。
嗯...枚举值通常需要可序列化,而符号序列化和反序列化并不那么方便。
只是我还是Object.freeze只针对那些不接受“猴子补丁自担风险”是 JS 的社会契约这一事实的人?
J
Jack G

𝗦𝗲𝗹𝗳-𝗗𝗲𝘀𝗰𝗿𝗶𝗽𝘁𝗶𝘃𝗲𝗗𝗲𝘀𝗰𝗿𝗶𝗽𝘁𝗶𝘃𝗲𝗩𝗮𝗿𝗶𝗮𝗯𝗹𝗲𝗡𝗮𝗺𝗲𝘀

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

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

确定枚举组的名称。想一个可以描述枚举目的的名词,或者至少可以描述枚举中的条目。例如,一组表示用户可选择颜色的枚举可能比 COLORS 更好地命名为 COLORCHOICES。决定组中的枚举是互斥的还是独立的。如果互斥,则以 ENUM_ 开头每个枚举变量名称。如果独立或并排,请使用 INDEX_。对于每个条目,创建一个名称以 ENUM_ 或 INDEX_ 开头的新局部变量,然后是组名称,然后是下划线,然后是属性的唯一友好名称 添加 ENUMLENGTH_、ENUMLEN_、INDEXLENGTH_ 或 INDEXLEN_(无论是 LEN_ 还是LENGTH_ 是个人喜好)最后的枚举变量。您应该在代码中尽可能使用此变量,以确保向枚举添加额外条目并增加此值不会破坏您的代码。给每个连续的枚举变量一个比上一个大一的值,从 0 开始。这个页面上有评论说 0 不应该用作枚举值,因为 0 == null, 0 == false, 0 == "" ,和其他 JS 的疯狂。我向您提交的是,为了避免这个问题并同时提高性能,请始终使用 === 并且永远不要让 == 出现在您的代码中,除非使用 typeof(例如 typeof X == "string")。在我使用 === 的所有岁月中,我从来没有遇到过使用 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_ 可能适合作为索引,例如在计算每个项目的出现次数时。

常量 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

请注意,在上面的代码中,添加一种新的宠物非常容易:您只需在 ENUM_PET_RAT 之后附加一个新条目并相应地更新 ENUMLEN_PET。在其他枚举系统中添加新条目可能更加困难和错误。

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

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

https://i.stack.imgur.com/xIHxl.png

(function(window){ "use strict"; var parseInt = window.parseInt; // 表示数组实例中的索引时使用 INDEX_ const INDEX_PIXELCOLOR_TYPE = 0, // 是一个 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, //在表示互斥物种或类型时使用 ENUM_ 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) { 结果[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA; 结果[INDEX_ALPHACOLOR_R] = parseInt(ra wstr.substring(0,2), 16);结果[INDEX_ALPHACOLOR_G] = parseInt(rawstr.substring(2,4), 16);结果[INDEX_ALPHACOLOR_B] = parseInt(rawstr.substring(4,6), 16);结果[INDEX_ALPHACOLOR_A] = parseInt(rawstr.substring(4,6), 16); } else if (rawstr.length === 4) { 结果[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;结果[INDEX_ALPHACOLOR_R] = parseInt(rawstr[0], 16) * 0x11;结果[INDEX_ALPHACOLOR_G] = parseInt(rawstr[1], 16) * 0x11;结果[INDEX_ALPHACOLOR_B] = parseInt(rawstr[2], 16) * 0x11;结果[INDEX_ALPHACOLOR_A] = parseInt(rawstr[3], 16) * 0x11; } else if (rawstr.length === 6) { 结果[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;结果[INDEX_SOLIDCOLOR_R] = parseInt(rawstr.substring(0,2), 16);结果[INDEX_SOLIDCOLOR_G] = parseInt(rawstr.substring(2,4), 16);结果[INDEX_SOLIDCOLOR_B] = parseInt(rawstr.substring(4,6), 16); } else if (rawstr.length === 3) { 结果[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;结果[INDEX_SOLIDCOLOR_R] = parseInt(rawstr[0], 16) * 0x11;结果[INDEX_SOLIDCOLOR_G] = parseInt(rawstr[1], 16) * 0x11;结果[INDEX_SOLIDCOLOR_B] = parseInt(rawstr[2], 16) * 0x11; } else { 结果[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_UNKNOWN; } 返回结果; } // 绿色的红色部分 console.log(parseHexColor("#0f0")[INDEX_SOLIDCOLOR_R]); // 透明紫色的 alpha console.log(parseHexColor("#f0f7")[INDEX_ALPHACOLOR_A]); // turquoise 的枚举数组 console.log(parseHexColor("#40E0D0")); })(自己);

(长度:2,450 字节)

有人可能会说这不如其他解决方案实用:它浪费大量空间,编写时间很长,而且它没有涂上糖语法。如果他们不缩小代码,这些人是对的。但是,没有一个理性的人会在最终产品中留下未缩小的代码。对于这个缩小,闭包编译器是我还没有找到的最好的。可以找到在线访问here。 Closure 编译器能够获取所有这些枚举数据并将其内联,从而使您的 Javascript 变得超级小,运行速度超级快。因此,使用闭包编译器进行缩小。观察。

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

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

https://i.stack.imgur.com/2cadt.jpg

'使用严格';(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.日志(d(“#40E0D0”))})(自我);

(长度:605 字节)

Closure Compiler 奖励您更聪明地编码和更好地组织代码,因为虽然许多压缩器会使用更大的压缩文件大小来惩罚有组织的代码,但如果您使用技巧,Closure Compiler 能够筛选您所有的清洁度和健全性以输出更小的文件大小像变量名枚举。在这个想法中,这就是编码的圣杯:一个工具既可以帮助您的代码缩小尺寸,又可以通过训练更好的编程习惯来帮助您的大脑。

𝗦𝗺𝗮𝗹𝗹𝗲𝗿 𝗖𝗼𝗱𝗲 𝗦𝗶𝘇𝗲

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

Source Without Using Enumerations(长度:1,973 字节(比枚举代码短 477 字节!))
Minified Without Using Enumerations(长度:843 字节(238 字节比枚举代码长))

https://i.stack.imgur.com/DX0nA.png

正如所见,没有枚举,源代码更短,但代价是更大的缩小代码。我对你一无所知;但我确信我不会将源代码合并到最终产品中。因此,这种枚举形式要优越得多,因为它会导致更小的压缩文件大小。

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

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

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

人们可能会反对带有枚举的代码似乎比带有普通对象的代码长得多,但看起来可能具有欺骗性。重要的是要记住,在使用史诗闭包编译器时,源代码大小与输出大小不成比例。观察。

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

上面是没有枚举的缩小代码,下面是带有枚举的缩小代码。

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

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

𝗘𝗮𝘀𝘆 𝗗𝗲𝗯𝘂𝗴𝗴𝗶𝗻𝗴

此外,这个人的个人顶部的樱桃正在使用这种枚举形式以及 Javascript 模式下的 CodeMirror 文本编辑器。 CodeMirror 的 Javascript 语法高亮模式高亮显示当前范围内的局部变量。这样,当您正确输入变量名时,您会立即知道,因为如果变量名先前使用 var 关键字声明,那么变量名会变为特殊颜色(默认为青色)。即使您不使用 CodeMirror,但在执行枚举名称错误的代码时,至少浏览器会抛出一个有用的 [variable name] is not defined 异常。此外,JSLint 和 Closure Compiler 等 JavaScript 工具会非常大声地告诉您何时输入错误的枚举变量名称。 CodeMirror、浏览器和各种 Javascript 工具放在一起使调试这种枚举形式非常简单且非常容易。

https://i.stack.imgur.com/sSyEB.png

常量 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) { // 不管 } if(currentColor === ENUM_COLORENUM_DNE) { // 不管 }

在上面的代码段中,您收到错误警报,因为 ENUM_COLORENUM_DNE 不存在。

𝗖𝗼𝗻𝗰𝗹𝘂𝘀𝗶𝗼𝗻

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


嗯。比起代码大小,我更喜欢可读性、易用性和理解性。
@Andrew 根据我的回答,您可以两者兼得。我的答案是最容易使用/管理的代码和最小的缩小代码大小。🙂
@Andrew我已尝试将您的 Yet Another Enum (YEA!) 应用于我的答案中的颜色解析器示例。但是,我发现了一些您可能想要解决的问题。 YEA 无法使用子类扩展枚举,这迫使我创建单独的父类和子类,这在大型项目中可能很难管理。 YEA 不能确保条目存在(例如 colors.REED 产生 undefined),因此拼写错误会造成难以捉摸的难题。 YEA 不区分使用枚举作为索引和 ID,导致代码混乱,一切看起来都一样。 …
@Andrew ... YEA 阻碍了 Closure Compiler 的缩小能力。将带有 YEA(3549 字节)的源代码与带有 YEA(1344 字节)的缩小代码与我的解决方案(604 字节)进行比较。最后,YEA 涉及“按名称映射”,因为它将字符串名称与枚举 ID 分开。我的只考虑 ID,所以不需要“按名称映射”,从而使设计更简单,性能更好。感谢您分享您的解决方案,但它需要许多修复才能实用。
过多的帖子格式和代码作为图像。会推荐编辑。
j
j2k

使用 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”等):

// 创建枚举的类(13 行) // 随意将其添加到您的实用程序库中 // 您的代码库和利润!注意:由于 Proxies 是 ES6 // 特性,一些浏览器/客户端可能不支持它,并且 // 您可能需要使用类似 babel class Enum { // Enum 类实例化 JavaScript 代理对象的服务进行转译。 // 实例化一个 `Proxy` 对象需要两个参数, // 一个 `target` 对象和一个 `handler`。我们首先定义处理程序, // 然后使用处理程序实例化一个 Proxy。 // 代理处理程序只是一个对象,其属性 // 是定义代理行为的函数 // 当对其执行操作时。 // 对于枚举,我们需要定义行为,让我们检查 // 正在访问的枚举器以及正在设置的枚举器。这可以通过 // 定义“get”和“set”陷阱来完成。 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 httpMethods = new Enum({ DELETE: "DELETE", GET: "GET", OPTIONS: "OPTIONS", PATCH: "PATCH", POST: "POST", PUT: "PUT" }) // 完整性检查控制台。 log(httpMethods.DELETE) // 记录 "DELETE" try { httpMethods.delete = "delete" } catch (e) { console.log("Error: ", e.message) } // 抛出 "Cannot add/update properties在定义后的枚举实例上” try { console.log(httpMethods.delete) } catch (e) { console.log("Error: ", e.message) } // throws "No such enumerator: delete"

旁白:代理到底是什么?

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


如何做类似 myEnum.valueOf("someStringValue") 的事情?预期:如果输入字符串具有枚举器元素的值,则应返回该项目。如果没有项目具有该字符串值,则抛出异常。
@sscarduzio 您可以通过将其指定为 Enum 类的实例方法来覆盖默认的 valueOf 方法。但是,为什么要以这种方式访问它而不是仅通过点符号访问它呢?
我的枚举是 const logLevelEnum = new Enum({ INFO: "info", DEBUG: "debug"}) 并且我从输入中解析任意字符串“info”或“debug”。所以我需要类似 currentLogLevel = logLevelEnum.parseOrThrow(settings.get("log_level"))
为什么你不能只做logLevelEnum[settings.get("log_level")]?添加 parseOrThrow 只会重复代理陷阱已经为您做的事情。
D
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

如果您在枚举末尾添加 return this;,您可以这样做:var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW');
我没有考虑到这一点,因为这不是我正常的做事方式。但你是绝对正确的!我会编辑进去。
我真的很喜欢这个,虽然我不喜欢搞砸对象空间(使用全局函数 ENUM)。将此转换为 mkenum 函数并添加可选的数字赋值 => var mixedUp = mkenum('BLACK', {RED: 0x0F00, BLUE: 0X0F, GREEN: 0x0F0, WHITE: 0x0FFF, ONE: 1}, TWO, THREE, FOUR) ; // 添加我的代码作为下面的答案。谢谢。
老实说,我什至不再使用它了。我一直在使用 Google 的 Closure Compiler,如果您使用 Advanced 设置,它的效果会不太好(或者它只会使事情复杂化)。所以我刚刚回到标准对象表示法。
falsewritableenumerableconfigurable 的默认值。无需咀嚼默认值。
R
Rob Hardy

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

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

这使您可以同时查找返回 1 的 MyEnum.Bar 和返回“Bar”的 MyEnum[1],而不管声明的顺序如何。


加上 MyEnum["Bar"] 可以返回 1... <3 TypeScript 到目前为止...
当然,如果您实际上正在使用 Typescript:enum MyEnum { Foo, Bar, Foobar }
A
Abdennour TOUMI

ES7 中,您可以根据静态属性执行优雅的 ENUM:

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

然后

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

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

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

你能解释一下为什么有一个父类是一个优势吗?我觉得我错过了什么!
不要那样做。 new ColorEnum() 完全没有意义。
扩展枚举听起来很疯狂,真的
一旦语言本身不支持它就有意义保持这个约定并像这样使用!我同意!
我认为(?)OP 的目的是:纯静态的好处是它可以作为单例在任何地方使用,并且您不需要实例化该类 - OP 并不建议您做!我认为他的意思是超类 Enum 上有标准的 static 枚举器方法,如 getValues()getNames()iterate() 等。如果是这样,你就不要不必为每种新的 enum 重新实现它们。
h
hvdd

创建一个对象字面量:

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

const 不会使对象的属性不可变,它只是意味着变量 Modes 不能重新分配给其他东西。要使其更完整,请在 const 旁边使用 Object.freeze()
请不要使用 Object.freeze。它防止 Closure Compiler 内联对象。
C
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 类具有一些关联缓存

一些缺点:

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

仍然没有类型安全


Y
Yaroslav

如果您使用 Backbone,则可以使用 Backbone.Collection 免费获得完整的枚举功能(按 id、名称、自定义成员查找)。

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

X
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

@JackGiffin 我同意您的答案性能更高,并且我的答案可能会占用更多内存,尽管您不应该假设每个人都想要 C++ 实现它的方式的枚举。请尊重其他答案和可能更喜欢这个答案的开发人员。
D
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

V
Vivin Paliath

我想出了以 Java 中的枚举为模型的 this 方法。这些是类型安全的,因此您也可以执行 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); }
S
Shivanshu Goyal
var ColorEnum = {
    red: {},
    green: {},
    blue: {}
}

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


这个答案被低估了。它的简单性是我最喜欢的想法之一。在实践中,我认为我会坚持使用字符串,因为它现在更容易调试。
嗯,只要确保这段代码不会被调用两次......
M
Manohar Reddy Poreddy

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

在我目前的游戏项目中,我使用了以下,因为很少有客户还在使用 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
}

A
Andrew

我对任何答案都不满意,所以我制作了又一个枚举(是的!)。

这个实现:

使用更多最新的 JS

只需声明这一类即可轻松创建枚举

按名称 (colors.RED)、字符串 (colors["RED"]) 和索引 (colors[0]) 进行映射,但您只需将字符串作为数组传入

将等效的 toString() 和 valueOf() 函数绑定到每个枚举对象(如果不希望这样做,可以简单地删除它 - 虽然 JS 的开销很小)

具有可选的全局命名/按名称字符串存储

一旦创建就冻结枚举对象,使其无法修改

特别感谢 Andre 'Fi''s answer 的启发。

代码:

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

用法:

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

I
Ilya Gazman

最简单的解决方案:

创造

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

J
Joseph Merdrignac

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

}

C
Carl Smith

这可能很有用:

const [CATS, DOGS, BIRDS] = ENUM();

实现简单高效:

function * ENUM(count=1) { while(true) yield count++ }

生成器可以生成所需的确切整数序列,而无需知道有多少常量。它还可以支持一个可选参数,该参数指定从哪个(可能是负数)数字开始(默认为 1)。


@Carl Smith我可能错过了一些评论,但这是一个相当大的编辑?!
@Bergi,你是对的,但它仍然是相同的答案。我真的只是为生成器清洁器编写了代码,并添加了解释,但你是对的,这是一个很大的差异。
u
user2254487

一种快速简单的方法是:

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

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

该功能是不必要的,并且为您提供与 OP 发布的完全相同的结果。
B
Blake Bowen

以下是实现 TypeScript enums 的几种不同方法。

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

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

G
Gelin Luo

我刚刚发布了一个 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_enum 升级到 constjs,只需更改语句

var genEnum = require('gen_enum');

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

T
Tim Kara

我创建了一个 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# 中的枚举):

var val2 = enum1.item2;

获取一个值的名称(在为不同的名称放置相同的值时可能会产生歧义):

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

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

var arr = enum1.GetObjArr();

会产生:

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

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

var html = enum1.GetSelectOptionsHTML();

持有:

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

M
Marcus Junius Brutus

尽管 ES2015 支持 only static methods(而不是静态属性)(参见 here,第 15.2.2.2 节),但奇怪的是,您可以将以下内容与带有 es2015 预设的 Babel 一起使用:

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 的静态字段)。尽管如此,以下更精炼的代码仍具有以下优点:

不能再创建 CellState 类型的对象 您可以保证不会为两个枚举实例分配相同的代码实用程序方法来从字符串表示中获取枚举 返回枚举的所有实例的值函数不必创建返回上面的值,手动(且容易出错)的方式。 '使用严格'; class Status { constructor(code, displayName = code) { if (Status.INSTANCES.has(code)) throw new Error(`duplicate code value: [${code}]`); if (!Status.canCreateMoreInstances) throw new Error(`在创建所有静态实例后尝试调用构造函数(${code}`+ `, ${displayName})`); this.code = 代码; this.displayName = 显示名称; Object.freeze(this); Status.INSTANCES.set(this.code, this); } toString() { return `[code: ${this.code}, displayName: ${this.displayName}]`; } 静态实例 = 新地图();静态 canCreateMoreInstances = true; // 值:静态 ARCHIVED = new Status('Archived');静态观察=新状态('观察');静态 SCHEDULED = new Status('Scheduled');静态 UNOBSERVED = new Status('Unobserved');静态 UNTRIGGERED = new Status('Untriggered');静态值 = function() { return Array.from(Status.INSTANCES.values()); } static fromCode(code) { if (!Status.INSTANCES.has(code)) throw new Error(`unknown code: ${code}`);否则返回 Status.INSTANCES.get(code); } } Status.canCreateMoreInstances = false;对象.冻结(状态);出口.状态=状态;


好的例子 :-)
J
Julius Dzidzevičius

这就是 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

L
LNT

你可以做这样的事情

    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