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

IT技术 javascript
2021-01-15 08:51:40

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

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

// later on

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

或者我还有其他方法可以做到这一点吗?

6个回答

从 1.8.5 开始可以密封和冻结 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这样的工具

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

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

这不是一个很好的答案,但我会说,就个人而言,这很好用

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

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

更新

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


已经可以警告名称:

if (currentColor == my.namespace.ColorEnum.RED) {
   // alert name of currentColor (RED: 0)
   var col = my.namespace.ColorEnum;
   for (var name in col) {
     if (col[name] == col.RED)
       alert(name);
   }
}

或者,您可以创建 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

@Stijin,真的很喜欢你更新的解决方案。在您博客的评论中发布了代码,并作为下面的评论。基本上,使用函数,从现有哈希列表执行属性构建,并可选择将其冻结(我的列表中的 mkenum_2)。干杯。
2021-03-23 08:51:40
+1 使用属性方法链接到您的帖子。优雅之处在于基本声明很简单,就像在 OP 中一样,在需要时添加了属性功能。
2021-03-29 08:51:40
@Johanisma:这个用例对枚举没有意义,因为它们的整个想法是你事先知道所有的值。然而,没有什么能阻止您稍后在 Javascript 中添加额外的值。我将在我的答案中添加一个例子。
2021-04-02 08:51:40
还有一个实现它的库,还包括很好的比较和反向搜索功能:github.com/adrai/enum
2021-04-06 08:51:40
那么如果你只有它的名字,你将如何去创建一个 SIZE 呢?
2021-04-08 08:51:40

底线:你不能。

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

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

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

所以不要将值映射到对象属性。使用 getter 访问枚举(存储为“私有”对象的属性)。一个天真的实现看起来像 -var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1
2021-03-16 08:51:40
@Scott Evernden:采取点。@kangax:关键是它仍然是一个黑客。枚举在 Javascript、句号、故事结尾中根本不存在。即使是 Tim Sylvester 建议的模式,也仍然不是一个理想的 hack。
2021-03-24 08:51:40
用文字散布代码不是很容易维护,因此为它创建常量是有意义的。当然,Javascript 也没有常量。所以基本上这只是编写干净代码的一种方式。它不能被强制执行,但在 Javascript 中可以执行的不多。您可以重新定义常量或函数,或几乎任何东西。例如:document.getElementById = function() {alert("你被搞砸了。Javascript 不是类型安全的。");};
2021-04-03 08:51:40
javascript 有类型安全吗?
2021-04-08 08:51:40
@Randolpho:Artur Czajka 的 Object.freeze 怎么样?这不会阻止您将星期一设置为星期四吗?
2021-04-11 08:51:40

这是我们都想要的:

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');
@Andrew 我创建了一个单独的、经过深思熟虑、仔细考虑和彻底审查的答案,我在生产中多次使用了这个答案:stackoverflow.com/a/50355530/5601591
2021-03-23 08:51:40
@Marquizzo(和 OP)我根据这个答案创建了一个改进版本:stackoverflow.com/a/60309416/1599699
2021-03-31 08:51:40