以下内容是否会使对象满足枚举在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
}
还是有其他方法可以做到这一点?
当前回答
es7方式,(迭代器,冻结),用法:
const ThreeWiseMen = new Enum('Melchior', 'Caspar', 'Balthazar')
for (let name of ThreeWiseMen)
console.log(name)
// with a given key
let key = ThreeWiseMen.Melchior
console.log(key in ThreeWiseMen) // true (string conversion, also true: 'Melchior' in ThreeWiseMen)
for (let entry from key.enum)
console.log(entry)
// prevent alteration (throws TypeError in strict mode)
ThreeWiseMen.Me = 'Me too!'
ThreeWiseMen.Melchior.name = 'Foo'
代码:
class EnumKey {
constructor(props) { Object.freeze(Object.assign(this, props)) }
toString() { return this.name }
}
export class Enum {
constructor(...keys) {
for (let [index, key] of keys.entries()) {
Object.defineProperty(this, key, {
value: new EnumKey({ name:key, index, enum:this }),
enumerable: true,
})
}
Object.freeze(this)
}
*[Symbol.iterator]() {
for (let key of Object.keys(this))
yield this[key]
}
toString() { return [...this].join(', ') }
}
其他回答
在我看来,枚举是什么?它是一个不可变的对象,总是可以访问,您可以相互比较项目,但项目具有通用的财产/方法,但对象本身或值不能更改,它们只能实例化一次。
枚举用于比较数据类型、设置、采取/回复的操作等。
为此,您需要具有相同实例的对象,以便您可以检查它是否为枚举类型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"))});
在大多数现代浏览器中,有一种符号基元数据类型,可用于创建枚举。它将确保枚举的类型安全,因为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
这是我对一个(标记的)Enum工厂的看法。这是一个工作演示。
/*
* Notes:
* The proxy handler enables case insensitive property queries
* BigInt is used to enable bitflag strings /w length > 52
*/
function EnumFactory() {
const proxyfy = {
construct(target, args) {
const caseInsensitiveHandler = {
get(target, key) {
return target[key.toUpperCase()] || target[key];
}
};
const proxified = new Proxy(new target(...args), caseInsensitiveHandler );
return Object.freeze(proxified);
},
}
const ProxiedEnumCtor = new Proxy(EnumCtor, proxyfy);
const throwIf = (
assertion = false,
message = `Unspecified error`,
ErrorType = Error ) =>
assertion && (() => { throw new ErrorType(message); })();
const hasFlag = (val, sub) => {
throwIf(!val || !sub, "valueIn: missing parameters", RangeError);
const andVal = (sub & val);
return andVal !== BigInt(0) && andVal === val;
};
function EnumCtor(values) {
throwIf(values.constructor !== Array ||
values.length < 2 ||
values.filter( v => v.constructor !== String ).length > 0,
`EnumFactory: expected Array of at least 2 strings`, TypeError);
const base = BigInt(1);
this.NONE = BigInt(0);
values.forEach( (v, i) => this[v.toUpperCase()] = base<<BigInt(i) );
}
EnumCtor.prototype = {
get keys() { return Object.keys(this).slice(1); },
subset(sub) {
const arrayValues = this.keys;
return new ProxiedEnumCtor(
[...sub.toString(2)].reverse()
.reduce( (acc, v, i) => ( +v < 1 ? acc : [...acc, arrayValues[i]] ), [] )
);
},
getLabel(enumValue) {
const tryLabel = Object.entries(this).find( value => value[1] === enumValue );
return !enumValue || !tryLabel.length ?
"getLabel: no value parameter or value not in enum" :
tryLabel.shift();
},
hasFlag(val, sub = this) { return hasFlag(val, sub); },
};
return arr => new ProxiedEnumCtor(arr);
}
var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });
您不需要指定id,只需使用空对象来比较枚举即可。
if (incommingEnum === DaysEnum.monday) //incommingEnum is monday
EDIT:如果要序列化对象(例如JSON),将再次输入id。
(摘自加布里埃尔·利亚马斯评论)(根据Stijn de Witt的评论进行编辑)
这不是一个很好的答案,但我个人认为这很好
话虽如此,因为值是什么并不重要(您使用了0、1、2),所以我会使用一个有意义的字符串,以防您想要输出当前值。