在JavaScript中定义枚举的首选语法是什么?就像是:
my.namespace.ColorEnum = { RED : 0, GREEN : 1, BLUE : 2 } // later on if(currentColor == my.namespace.ColorEnum.RED) { // whatever }
还是有更优选的成语?
由于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
确保更强的类型安全性(使用枚举或其他方式)的一种方法是使用TypeScript或Flow等工具.
资源
不需要引用,但我保持它们的一致性.
这不是一个很好的答案,但我会说,个人工作得很好
话虽如此,因为值是什么并不重要(你使用了0,1,2),我会使用一个有意义的字符串,以防你想输出当前值.
更新:感谢所有人的支持,但我认为下面的答案不再是在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"};
请记住,身份检查不需要枚举的字段(本例中的值,名称和代码),只是为了方便起见.此外,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简单但强大的命名空间和依赖管理:包JS
底线:你不能.
你可以伪造它,但你不会得到类型安全.通常,这是通过创建映射到整数值的字符串值的简单字典来完成的.例如:
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
虽然它可能不适合枚举的每一个有效用途,但它有很长的路要走.
这就是我们所有人想要的:
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 */ };
编辑 - 小改进 - 现在使用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');
在大多数现代浏览器中,有一个符号原始数据类型,可用于创建枚举.它将确保枚举的类型安全性,因为JavaScript保证每个符号值都是唯一的,即Symbol() != Symbol()
.例如:
const COLOR = Object.freeze({RED: Symbol(), BLUE: Symbol()});
要简化调试,可以向枚举值添加说明:
const COLOR = Object.freeze({RED: Symbol("RED"), BLUE: Symbol("BLUE")});
Plunker演示
在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
我一直在玩这个,因为我喜欢我的名词.=)
使用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
大多数人的"首选语法"已在上面列出.但是,存在一个主要的首要问题:性能.上述答案中没有一个在最轻微的情况下非常高效,它们都会使代码大小膨胀到极致.为了实现真正的性能,易于阅读代码,以及通过缩小来实现前所未有的代码大小减少,这是进行枚举的正确方法.
const ENUM_COLORENUM_RED = 0, ENUM_COLORENUM_GREEN = 1, ENUM_COLORENUM_BLUE = 2, ENUMLEN_COLORENUM = 3; // later on if(currentColor === ENUM_COLORENUM_RED) { // whatever }
此外,此语法允许清晰简洁的类扩展,如下所示.
(长度:2,450字节)
// Precondition: var arr = []; // arr[INDEX_] = ENUM_;
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', });
完整说明:
从传统语言中获得的Enums的一个非常有用的功能是,如果您尝试访问不存在的枚举器,它们会爆炸(抛出编译时错误).
除了冻结模拟的枚举结构以防止意外/恶意添加其他值之外,其他任何答案都没有解决Enums的内在特性.
您可能已经意识到,在JavaScript中访问不存在的成员只会返回undefined
并且不会破坏您的代码.由于枚举数是预定义的常量(即一周中的几天),因此永远不应该存在未定义枚举数的情况.
不要误会我的意思,JavaScript undefined
在访问未定义的属性时返回的行为实际上是语言的一个非常强大的功能,但是当你试图模仿传统的Enum结构时,它不是你想要的功能.
这是Proxy对象闪耀的地方.通过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"
这是我所知道的一个旧版本,但它通过TypeScript接口实现的方式是:
var MyEnum; (function (MyEnum) { MyEnum[MyEnum["Foo"] = 0] = "Foo"; MyEnum[MyEnum["FooBar"] = 2] = "FooBar"; MyEnum[MyEnum["Bar"] = 1] = "Bar"; })(MyEnum|| (MyEnum= {}));
这使您可以查看MyEnum.Bar
哪两个返回1,并MyEnum[1]
返回"Bar"而不管声明的顺序如何.
这是我使用的解决方案.
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类有一些关联缓存
一些缺点:
一些杂乱的内存管理在那里进行,因为我保留了对枚举的引用
仍然没有类型安全
在ES7中,您可以依靠静态属性执行优雅的ENUM:
class ColorEnum { static RED = 0 ; static GREEN = 1; static BLUE = 2; }
然后
if (currentColor === ColorEnum.GREEN ) {/*-- coding --*/}
(使用类而不是文字对象)的优点是拥有一个父类,Enum
然后所有的Enums都将扩展该类.
class ColorEnum extends Enum {/*....*/}
创建一个对象文字:
const Modes = { DRAGGING: 'drag', SCALING: 'scale', CLICKED: 'click' };
如果您使用的骨干,你可以得到全面的枚举功能免费使用(通过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()
你的答案太复杂了
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
我修改了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
IE8不支持freeze()方法.
来源: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 }
我想出了这种方法,该方法以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 } })();