以下内容是否会使对象满足枚举在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
}
还是有其他方法可以做到这一点?
当前回答
这是我使用的解决方案。
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
});
我通常使用最后两种方法从消息对象映射enum。
这种方法的一些优点:
易于声明枚举易于访问您的枚举枚举可以是复杂类型如果经常使用getByValue,Enum类具有一些关联缓存
一些缺点:
由于我保留了对枚举的引用,内存管理有些混乱仍然没有类型安全性
其他回答
我修改了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
你可以试试这个:
var Enum = Object.freeze({
Role: Object.freeze({ Administrator: 1, Manager: 2, Supervisor: 3 }),
Color:Object.freeze({RED : 0, GREEN : 1, BLUE : 2 })
});
alert(Enum.Role.Supervisor);
alert(Enum.Color.GREEN);
var currentColor=0;
if(currentColor == Enum.Color.RED) {
alert('Its Red');
}
这是我对一个(标记的)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);
}
更新05.11.2020:修改为包含静态字段和方法,以更接近地复制“true”枚举行为。
是否有人尝试过使用包含私有字段和“get”访问器的类执行此操作?我意识到私有类字段目前仍处于实验阶段,但它似乎可以用于创建具有不可变字段/财产的类。浏览器支持也不错。唯一不支持它的“主要”浏览器是Firefox(我相信他们很快就会支持)和IE(谁在乎)。
免责声明:我不是一个开发者。我只是在寻找这个问题的答案,并开始思考我有时如何通过创建带有私有字段和受限属性访问器的类来在C#中创建“增强”枚举。
样本类别
class Sizes {
// Private Fields
static #_SMALL = 0;
static #_MEDIUM = 1;
static #_LARGE = 2;
// Accessors for "get" functions only (no "set" functions)
static get SMALL() { return this.#_SMALL; }
static get MEDIUM() { return this.#_MEDIUM; }
static get LARGE() { return this.#_LARGE; }
}
现在您应该可以直接调用enums。
Sizes.SMALL; // 0
Sizes.MEDIUM; // 1
Sizes.LARGE; // 2
使用私有字段和有限访问器的组合意味着枚举值得到了很好的保护。
Sizes.SMALL = 10 // Sizes.SMALL is still 0
Sizes._SMALL = 10 // Sizes.SMALL is still 0
Sizes.#_SMALL = 10 // Sizes.SMALL is still 0
从1.8.5开始,可以密封和冻结物体,因此定义如下:
const DaysEnum = Object.freeze({"monday":1, "tuesday":2, "wednesday":3, ...})
or
const DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Object.freeze(DaysEnum)
瞧!JS枚举。
然而,这并不能阻止您为变量赋值,这通常是enums的主要目标:
let day = DaysEnum.tuesday
day = 298832342 // goes through without any errors
确保更强的类型安全性(使用enums或其他)的一种方法是使用TypeScript或Flow等工具。
不需要引用,但我保留了它们以保持一致性。