以下内容是否会使对象满足枚举在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
}
还是有其他方法可以做到这一点?
当前回答
我一直在摆弄这个,因为我喜欢我的遗珠
使用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
});
由于属性可写:false,这应该使其类型安全。
因此,您应该能够创建一个自定义对象,然后对其调用Enum()。分配的值从0开始,每项递增。
var EnumColors={};
EnumColors.Enum('RED','BLUE','GREEN','YELLOW');
EnumColors.RED; // == 0
EnumColors.BLUE; // == 1
EnumColors.GREEN; // == 2
EnumColors.YELLOW; // == 3
其他回答
你的答案太复杂了
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
一种快速而简单的方法是:
var Colors = function(){
return {
'WHITE':0,
'BLACK':1,
'RED':2,
'GREEN':3
}
}();
console.log(Colors.WHITE) //this prints out "0"
使用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:代理到底是什么?
我记得当我第一次开始在任何地方看到代理这个词的时候,我很长一段时间都觉得它毫无意义。如果你现在是这样的话,我认为概括代理的一个简单方法是将它们视为软件、机构,甚至是充当两个服务器、公司或人员之间的中间人或中间人的人。
真的很像@Duncan上面所做的,但我不喜欢用Enum破坏全局Object函数空间,所以我写了以下内容:
function mkenum_1()
{
var o = new Object();
var c = -1;
var f = function(e, v) { Object.defineProperty(o, e, { value:v, writable:false, enumerable:true, configurable:true })};
for (i in arguments) {
var e = arguments[i];
if ((!!e) & (e.constructor == Object))
for (j in e)
f(j, (c=e[j]));
else
f(e, ++c);
}
return Object.freeze ? Object.freeze(o) : o;
}
var Sizes = mkenum_1('SMALL','MEDIUM',{LARGE: 100},'XLARGE');
console.log("MED := " + Sizes.MEDIUM);
console.log("LRG := " + Sizes.LARGE);
// Output is:
// MED := 1
// LRG := 100
@Stijin也有一个简洁的解决方案(参考他的博客),其中包括这些对象的财产。我也为此写了一些代码,接下来我会把它包括在内。
function mkenum_2(seed)
{
var p = {};
console.log("Seed := " + seed);
for (k in seed) {
var v = seed[k];
if (v instanceof Array)
p[(seed[k]=v[0])] = { value: v[0], name: v[1], code: v[2] };
else
p[v] = { value: v, name: k.toLowerCase(), code: k.substring(0,1) };
}
seed.properties = p;
return Object.freeze ? Object.freeze(seed) : seed;
}
此版本生成了一个允许友好名称转换和短代码的附加属性列表。我喜欢这个版本,因为不需要像代码那样在财产中重复数据输入。
var SizeEnum2 = mkenum_2({ SMALL: 1, MEDIUM: 2, LARGE: 3});
var SizeEnum3 = mkenum_2({ SMALL: [1, "small", "S"], MEDIUM: [2, "medium", "M"], LARGE: [3, "large", "L"] });
这两者可以组合成一个处理单元mkenum(使用enum、分配值、创建和添加属性列表)。然而,由于我今天已经在这方面花了太多时间,我将把这个组合作为练习留给亲爱的读者。
我制作了一个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>..."