以下内容是否会使对象满足枚举在JavaScript中的所有特性?类似于:

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

// later on

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

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


当前回答

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

此实施:

使用最新的JS只需要这一类的声明即可轻松创建enum具有按名称(colors.RED)、字符串(colors[“RED”])和索引(colors[0])进行的映射,但只需将字符串作为数组传递将等价于String()和valueOf()函数绑定到每个enum对象(如果不希望这样做,可以简单地删除它-不过JS的开销很小)具有可选的全局命名/存储(按名称字符串)创建后冻结枚举对象,使其无法修改

特别感谢Andre‘Fi的回答给了我们一些启发。


代码:

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

其他回答

这是我使用的解决方案。

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

我通常使用最后两种方法从消息对象映射enum。

这种方法的一些优点:

易于声明枚举易于访问您的枚举枚举可以是复杂类型如果经常使用getByValue,Enum类具有一些关联缓存

一些缺点:

由于我保留了对枚举的引用,内存管理有些混乱仍然没有类型安全性

使用Javascript代理

TLDR:将这个类添加到实用程序方法中,并在整个代码中使用它,它模拟了传统编程语言中的Enum行为,当您尝试访问不存在的枚举器或添加/更新枚举器时,实际上会抛出错误。无需依赖Object.creeze()。

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

完整解释:

从传统语言中获得的Enum的一个非常有益的特性是,如果您试图访问不存在的枚举器,它们就会爆炸(引发编译时错误)。

除了冻结模拟枚举结构以防止意外/恶意添加附加值之外,其他答案都没有解决枚举的固有特性。

正如您可能知道的,在JavaScript中访问不存在的成员只会返回undefined,不会破坏代码。由于枚举数是预定义的常量(即一周中的几天),因此不应出现枚举数未定义的情况。

不要误解我的意思,JavaScript在访问未定义的财产时返回未定义的行为实际上是语言的一个非常强大的功能,但当您试图模拟传统Enum结构时,这并不是您想要的功能。

这是代理对象发光的地方。随着ES6(ES2015)的引入,代理被标准化为语言。以下是MDN的描述:

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

与web服务器代理类似,JavaScript代理能够拦截对象上的操作(使用“陷阱”,如果您愿意,可以将其称为钩子),并允许您在操作完成之前执行各种检查、操作和/或操作(或者在某些情况下完全停止操作,这正是我们在尝试引用不存在的枚举器时想要做的)。

下面是一个精心设计的示例,它使用Proxy对象来模拟Enum。本例中的枚举器是标准HTTP方法(即“GET”、“POST”等):

//用于创建枚举的类(13行)//请随意将其添加到中的实用程序库//你的代码库和利润!注:因为代理是ES6//功能,某些浏览器/客户端可能不支持//您可能需要使用babel这样的服务进行transfile类枚举{//Enum类实例化JavaScript代理对象。//实例化“代理”对象需要两个参数,//“target”对象和“handler”。我们首先定义处理程序,//然后使用处理程序实例化代理。//代理处理程序只是一个对象,它的财产//是定义代理行为的函数//当对其执行操作时。//对于枚举,我们需要定义允许我们检查枚举器的行为//正在访问以及正在设置的枚举器。这可以通过以下方式实现//定义“get”和“set”陷阱。构造函数(enumObj){常量处理程序={get(目标,名称){if(目标类型[名称]!='undefined'){返回目标[名称]}抛出新错误(`没有这样的枚举器:${name}`)},设置(){throw new Error(“定义枚举实例后无法在其上添加/更新财产”)}}//冻结目标对象以防止修改返回新代理(enumObj,处理程序)}}//现在我们有了创建Enum的通用方法,让我们创建第一个Enum!const httpMethods=新枚举({DELETE:“删除”,GET:“GET”,OPTIONS:“选项”,PATCH:“补丁”,POST:“POST”,PUT:“PUT”})//卫生检查console.log(httpMethods.DELETE)//日志“DELETE”尝试{httpMethods.delete=“删除”}捕获(e){console.log(“错误:”,e.message)}//throws“定义Enum实例后,无法在其上添加/更新财产”尝试{console.log(httpMethods.delete)}捕获(e){console.log(“错误:”,e.message)}//throws“没有这样的枚举器:delete”


ASIDE:代理到底是什么?

我记得当我第一次开始在任何地方看到代理这个词的时候,我很长一段时间都觉得它毫无意义。如果你现在是这样的话,我认为概括代理的一个简单方法是将它们视为软件、机构,甚至是充当两个服务器、公司或人员之间的中间人或中间人的人。

你可以试试这个:

   var Enum = Object.freeze({
            Role: Object.freeze({ Administrator: 1, Manager: 2, Supervisor: 3 }),
            Color:Object.freeze({RED : 0, GREEN : 1, BLUE : 2 })
            });

    alert(Enum.Role.Supervisor);
    alert(Enum.Color.GREEN);
    var currentColor=0;
    if(currentColor == Enum.Color.RED) {
       alert('Its Red');
    }

底线:你不能。

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

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

虽然它可能不适合枚举的每一种有效使用,但它需要很长的路。

从1.8.5开始,可以密封和冻结物体,因此定义如下:

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

or

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

瞧!JS枚举。

然而,这并不能阻止您为变量赋值,这通常是enums的主要目标:

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

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

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