以下内容是否会使对象满足枚举在JavaScript中的所有特性?类似于:
my.namespace.ColorEnum = {
RED : 0,
GREEN : 1,
BLUE : 2
}
// later on
if(currentColor == my.namespace.ColorEnum.RED) {
// whatever
}
还是有其他方法可以做到这一点?
以下内容是否会使对象满足枚举在JavaScript中的所有特性?类似于:
my.namespace.ColorEnum = {
RED : 0,
GREEN : 1,
BLUE : 2
}
// later on
if(currentColor == my.namespace.ColorEnum.RED) {
// whatever
}
还是有其他方法可以做到这一点?
当前回答
使用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:代理到底是什么?
我记得当我第一次开始在任何地方看到代理这个词的时候,我很长一段时间都觉得它毫无意义。如果你现在是这样的话,我认为概括代理的一个简单方法是将它们视为软件、机构,甚至是充当两个服务器、公司或人员之间的中间人或中间人的人。
其他回答
创建对象文字:
const Modes = {
DRAGGING: 'drag',
SCALING: 'scale',
CLICKED: 'click'
};
在大多数现代浏览器中,有一种符号基元数据类型,可用于创建枚举。它将确保枚举的类型安全,因为JavaScript保证每个符号值都是唯一的,即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
一种快速而简单的方法是:
var Colors = function(){
return {
'WHITE':0,
'BLACK':1,
'RED':2,
'GREEN':3
}
}();
console.log(Colors.WHITE) //this prints out "0"
底线:你不能。
你可以伪造它,但你不会得到类型安全。通常,这是通过创建映射到整数值的字符串值的简单字典来完成的。例如:
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
虽然它可能不适合枚举的每一种有效使用,但它需要很长的路。
我制作了一个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>..."