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

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

// later on

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

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


当前回答

您可以使用一个简单的函数来反转键和值,当它将数字整数字符串转换为数字时,它也可以处理数组。代码小、简单,可用于此用例和其他用例。

var objInvert=函数(obj){var反转={}for(对象中的变量i){如果(i.match(/^\d+$/))i=parseInt(i,10)反转[obj[i]]=i}返回反转}var musicStyles=对象冻结(objInvert(['ROCK','SURF','METAL',“BOSSA-NOVA”、“op”、“die”]))console.log(音乐样式)

其他回答

这里有两种实现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

您可以使用一个简单的函数来反转键和值,当它将数字整数字符串转换为数字时,它也可以处理数组。代码小、简单,可用于此用例和其他用例。

var objInvert=函数(obj){var反转={}for(对象中的变量i){如果(i.match(/^\d+$/))i=parseInt(i,10)反转[obj[i]]=i}返回反转}var musicStyles=对象冻结(objInvert(['ROCK','SURF','METAL',“BOSSA-NOVA”、“op”、“die”]))console.log(音乐样式)

使用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:代理到底是什么?

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

更新

我认为我下面的答案不再是用JavaScript编写枚举的最佳方法。有关更多详细信息,请参阅我的博客文章:JavaScript中的枚举。


已经可以提醒名称:

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

或者,您可以创建值对象,这样您就可以吃蛋糕了:

var SIZE = {
  SMALL : {value: 0, name: "Small", code: "S"}, 
  MEDIUM: {value: 1, name: "Medium", code: "M"}, 
  LARGE : {value: 2, name: "Large", code: "L"}
};

var currentSize = SIZE.MEDIUM;
if (currentSize == SIZE.MEDIUM) {
  // this alerts: "1: Medium"
  alert(currentSize.value + ": " + currentSize.name);
}

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

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

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

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

您可以使用Object.protype.hasOwnProperty()

变量findInEnum,颜色枚举={红色:0,绿色:1,蓝色:2};//稍后findInEnum=函数(enumKey){if(colorEnum.hasOwnProperty(enumKey)){return enumKey+“值:”+colorEnum[nenumKey]}}警报(findInEnum(“蓝色”))