以下内容是否会使对象满足枚举在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
}
还是有其他方法可以做到这一点?
当前回答
我修改了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
其他回答
这里有两种实现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 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>..."
我编写了enumeranjs一个非常小的库来解决这个问题,它确保了类型安全,允许枚举常量从原型继承,保证枚举常量和枚举类型是不可变的+许多小特性。它允许重构大量代码,并在枚举定义中移动一些逻辑。下面是一个示例:
var CloseEventCodes = new Enumeration("closeEventCodes", {
CLOSE_NORMAL: { _id: 1000, info: "Connection closed normally" },
CLOSE_GOING_AWAY: { _id: 1001, info: "Connection closed going away" },
CLOSE_PROTOCOL_ERROR: { _id: 1002, info: "Connection closed due to protocol error" },
CLOSE_UNSUPPORTED: { _id: 1003, info: "Connection closed due to unsupported operation" },
CLOSE_NO_STATUS: { _id: 1005, info: "Connection closed with no status" },
CLOSE_ABNORMAL: { _id: 1006, info: "Connection closed abnormally" },
CLOSE_TOO_LARGE: { _id: 1009, info: "Connection closed due to too large packet" }
},{ talk: function(){
console.log(this.info);
}
});
CloseEventCodes.CLOSE_TOO_LARGE.talk(); //prints "Connection closed due to too large packet"
CloseEventCodes.CLOSE_TOO_LARGE instanceof CloseEventCodes //evaluates to true
枚举基本上是一个工厂。
此处提供完整记录的指南。希望这有帮助。
在我看来,枚举是什么?它是一个不可变的对象,总是可以访问,您可以相互比较项目,但项目具有通用的财产/方法,但对象本身或值不能更改,它们只能实例化一次。
枚举用于比较数据类型、设置、采取/回复的操作等。
为此,您需要具有相同实例的对象,以便您可以检查它是否为枚举类型if(something instanceof enum)此外,如果您获得了一个枚举对象,则无论枚举类型如何,它都应该以相同的方式进行响应。
在我的例子中,它比较数据类型的值,但它可以是任何东西,从在3d游戏中修改面向方向的块到将值传递到特定的对象类型注册表。
请记住,它是javascript,不提供固定的枚举类型,您最终总是自己实现,正如本线程所示,有很多实现都不正确。
这是我用于枚举的内容。由于枚举是不可变的(或者至少应该是呵呵),所以我冻结了对象,这样它们就不容易被操作了。
枚举可以由EnumField.STRING使用,并且它们有自己的方法来处理它们的类型。要测试是否有东西传递给对象,可以使用if(somevar instanceof EnumFieldSegment)
这可能不是最优雅的解决方案,我愿意改进,但这种类型的不可变枚举(除非你解冻它)正是我需要的用例。
我也意识到我可以用{}重写原型,但我的大脑在这种格式下工作得更好;-)向我开枪。
/**
* simple parameter object instantiator
* @param name
* @param value
* @returns
*/
function p(name,value) {
this.name = name;
this.value = value;
return Object.freeze(this);
}
/**
* EnumFieldSegmentBase
*/
function EnumFieldSegmentBase() {
this.fieldType = "STRING";
}
function dummyregex() {
}
dummyregex.prototype.test = function(str) {
if(this.fieldType === "STRING") {
maxlength = arguments[1];
return str.length <= maxlength;
}
return true;
};
dummyregexposer = new dummyregex();
EnumFieldSegmentBase.prototype.getInputRegex = function() {
switch(this.fieldType) {
case "STRING" : return dummyregexposer;
case "INT": return /^(\d+)?$/;
case "DECIMAL2": return /^\d+(\.\d{1,2}|\d+|\.)?$/;
case "DECIMAL8": return /^\d+(\.\d{1,8}|\d+|\.)?$/;
// boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its false, otherwise lets see what Boolean produces
case "BOOLEAN": return dummyregexposer;
}
};
EnumFieldSegmentBase.prototype.convertToType = function($input) {
var val = $input;
switch(this.fieldType) {
case "STRING" : val = $input;break;
case "INT": val==""? val=0 :val = parseInt($input);break;
case "DECIMAL2": if($input === "" || $input === null) {$input = "0"}if($input.substr(-1) === "."){$input = $input+0};val = new Decimal2($input).toDP(2);break;
case "DECIMAL8": if($input === "" || $input === null) {$input = "0"}if($input.substr(-1) === "."){$input = $input+0};val = new Decimal8($input).toDP(8);break;
// boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its false, otherwise lets see what Boolean produces
case "BOOLEAN": val = (typeof $input == 'boolean' ? $input : (typeof $input === 'string' ? (($input === "false" || $input === "" || $input === "0") ? false : true) : new Boolean($input).valueOf())) ;break;
}
return val;
};
EnumFieldSegmentBase.prototype.convertToString = function($input) {
var val = $input;
switch(this.fieldType) {
case "STRING": val = $input;break;
case "INT": val = $input+"";break;
case "DECIMAL2": val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+2 : $input.toString().indexOf('.')+2)) ;break;
case "DECIMAL8": val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+8 : $input.toString().indexOf('.')+8)) ;break;
case "BOOLEAN": val = $input ? "true" : "false" ;break;
}
return val;
};
EnumFieldSegmentBase.prototype.compareValue = function($val1,$val2) {
var val = false;
switch(this.fieldType) {
case "STRING": val = ($val1===$val2);break;
case "INT": val = ($val1===$val2);break;
case "DECIMAL2": val = ($val1.comparedTo($val2)===0);break;
case "DECIMAL8": val = ($val1.comparedTo($val2)===0);break;
case "BOOLEAN": val = ($val1===$val2);break;
}
return val;
};
/**
* EnumFieldSegment is an individual segment in the
* EnumField
* @param $array An array consisting of object p
*/
function EnumFieldSegment() {
for(c=0;c<arguments.length;c++) {
if(arguments[c] instanceof p) {
this[arguments[c].name] = arguments[c].value;
}
}
return Object.freeze(this);
}
EnumFieldSegment.prototype = new EnumFieldSegmentBase();
EnumFieldSegment.prototype.constructor = EnumFieldSegment;
/**
* Simple enum to show what type of variable a Field type is.
* @param STRING
* @param INT
* @param DECIMAL2
* @param DECIMAL8
* @param BOOLEAN
*
*/
EnumField = Object.freeze({STRING: new EnumFieldSegment(new p("fieldType","STRING")),
INT: new EnumFieldSegment(new p("fieldType","INT")),
DECIMAL2: new EnumFieldSegment(new p("fieldType","DECIMAL2")),
DECIMAL8: new EnumFieldSegment(new p("fieldType","DECIMAL8")),
BOOLEAN: new EnumFieldSegment(new p("fieldType","BOOLEAN"))});