我正在用Javascript重建一个旧的Java项目,并意识到在JS中没有很好的方法来枚举枚举。
我能想到的最好的是:
const Colors = {
RED: Symbol("red"),
BLUE: Symbol("blue"),
GREEN: Symbol("green")
};
Object.freeze(Colors);
的const
保持Colors
被重新分配,并冷冻它防止突变的键和值。我正在使用Symbols,所以它Colors.RED
不等于0
,或者除自身以外的其他任何符号。
这种配方有问题吗?有没有更好的办法?
(我知道这个问题有点重复,但是以前的所有Q / A都已经很老了,ES6为我们提供了一些新功能。)
编辑:
另一个解决序列化问题的解决方案,但我认为仍然存在领域问题:
const enumValue = (name) => Object.freeze({toString: () => name});
const Colors = Object.freeze({
RED: enumValue("Colors.RED"),
BLUE: enumValue("Colors.BLUE"),
GREEN: enumValue("Colors.GREEN")
});
通过使用对象引用作为值,您将获得与Symbols相同的避免碰撞的功能。
这种配方有问题吗?
我没看到。
有没有更好的办法?
我将两个语句合而为一:
const Colors = Object.freeze({
RED: Symbol("red"),
BLUE: Symbol("blue"),
GREEN: Symbol("green")
});
如果您不喜欢样板文件(如重复Symbol
调用),那么当然还可以编写一个帮助程序函数makeEnum
,该函数从名称列表中创建相同的东西。
虽然将Symbol
用作枚举值在简单的用例中效果很好,但为枚举赋予属性可能很方便。这可以通过使用Object
作为包含属性的枚举值来完成。
例如,我们可以给每个Colors
名称和十六进制值:
/**
* Enum for common colors.
* @readonly
* @enum {{name: string, hex: string}}
*/
const Colors = Object.freeze({
RED: { name: "red", hex: "#f00" },
BLUE: { name: "blue", hex: "#00f" },
GREEN: { name: "green", hex: "#0f0" }
});
在枚举中包含属性避免了必须编写switch
语句(扩展枚举时可能会忘记在switch语句中添加新的情况)。该示例还显示了用JSDoc枚举批注记录的枚举属性和类型。
平等与Colors.RED === Colors.RED
存在true
和Colors.RED === Colors.BLUE
存在一样可以预期地工作false
。
这是我个人的方法。
class ColorType {
static get RED () {
return "red";
}
static get GREEN () {
return "green";
}
static get BLUE () {
return "blue";
}
}
// Use case.
const color = Color.create(ColorType.RED);
如上所述,您还可以编写一个makeEnum()
辅助函数:
function makeEnum(arr){
let obj = {};
for (let val of arr){
obj[val] = Symbol(val);
}
return Object.freeze(obj);
}
像这样使用它:
const Colors = makeEnum(["red","green","blue"]);
let startColor = Colors.red;
console.log(startColor); // Symbol(red)
if(startColor == Colors.red){
console.log("Do red things");
}else{
console.log("Do non-red things");
}
检查TypeScript是如何做到的。基本上,他们执行以下操作:
const MAP = {};
MAP[MAP[1] = 'A'] = 1;
MAP[MAP[2] = 'B'] = 2;
MAP['A'] // 1
MAP[1] // A
使用符号,冻结对象,无论您想要什么。
如果您不需要纯ES6并可以使用Typescript,则可以使用它enum
:
https://www.typescriptlang.org/docs/handbook/enums.html
您可以检查Enumify,它是ES6枚举的一个非常好的功能齐全的库。
也许这个解决方案?:)
function createEnum (array) {
return Object.freeze(array
.reduce((obj, item) => {
if (typeof item === 'string') {
obj[item.toUpperCase()] = Symbol(item)
}
return obj
}, {}))
}
例:
createEnum(['red', 'green', 'blue']);
> {RED: Symbol(red), GREEN: Symbol(green), BLUE: Symbol(blue)}
您还可以使用es6-enum软件包(https://www.npmjs.com/package/es6-enum)。它很容易使用。请参阅以下示例:
import Enum from "es6-enum";
const Colors = Enum("red", "blue", "green");
Colors.red; // Symbol(red)
这是我在JavaScript中实现的Java枚举。
我还包括单元测试。
const main = () => {
mocha.setup('bdd')
chai.should()
describe('Test Color [From Array]', function() {
let Color = new Enum('RED', 'BLUE', 'GREEN')
it('Test: Color.values()', () => {
Color.values().length.should.equal(3)
})
it('Test: Color.RED', () => {
chai.assert.isNotNull(Color.RED)
})
it('Test: Color.BLUE', () => {
chai.assert.isNotNull(Color.BLUE)
})
it('Test: Color.GREEN', () => {
chai.assert.isNotNull(Color.GREEN)
})
it('Test: Color.YELLOW', () => {
chai.assert.isUndefined(Color.YELLOW)
})
})
describe('Test Color [From Object]', function() {
let Color = new Enum({
RED : { hex: '#F00' },
BLUE : { hex: '#0F0' },
GREEN : { hex: '#00F' }
})
it('Test: Color.values()', () => {
Color.values().length.should.equal(3)
})
it('Test: Color.RED', () => {
let red = Color.RED
chai.assert.isNotNull(red)
red.getHex().should.equal('#F00')
})
it('Test: Color.BLUE', () => {
let blue = Color.BLUE
chai.assert.isNotNull(blue)
blue.getHex().should.equal('#0F0')
})
it('Test: Color.GREEN', () => {
let green = Color.GREEN
chai.assert.isNotNull(green)
green.getHex().should.equal('#00F')
})
it('Test: Color.YELLOW', () => {
let yellow = Color.YELLOW
chai.assert.isUndefined(yellow)
})
})
mocha.run()
}
class Enum {
constructor(values) {
this.__values = []
let isObject = arguments.length === 1
let args = isObject ? Object.keys(values) : [...arguments]
args.forEach((name, index) => {
this.__createValue(name, isObject ? values[name] : null, index)
})
Object.freeze(this)
}
values() {
return this.__values
}
/* @private */
__createValue(name, props, index) {
let value = new Object()
value.__defineGetter__('name', function() {
return Symbol(name)
})
value.__defineGetter__('ordinal', function() {
return index
})
if (props) {
Object.keys(props).forEach(prop => {
value.__defineGetter__(prop, function() {
return props[prop]
})
value.__proto__['get' + this.__capitalize(prop)] = function() {
return this[prop]
}
})
}
Object.defineProperty(this, name, {
value: Object.freeze(value),
writable: false
})
this.__values.push(this[name])
}
/* @private */
__capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
}
main()
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.2.0/chai.js"></script>
<!--
public enum Color {
RED("#F00"),
BLUE("#0F0"),
GREEN("#00F");
private String hex;
public String getHex() { return this.hex; }
private Color(String hex) {
this.hex = hex;
}
}
-->
<div id="mocha"></div>
更新11.05.2020:
修改为包括静态字段和方法,以更接近地复制“真实”枚举行为。
如果您打算进行更新,建议您尝试使用所谓的“枚举类”(除非您不能接受任何浏览器或运行时环境限制)。基本上,这是一个非常简单和干净的类,使用私有字段和有限的访问器来模拟枚举的行为。当我想在枚举中构建更多功能时,有时会在C#中执行此操作。
我意识到私有类字段目前仍处于试验阶段,但它似乎可以用于创建具有不变字段/属性的类。浏览器支持也不错。唯一不支持它的“主要”浏览器是Firefox(我相信它们很快就会推出)和IE(谁在乎)。
免责声明:
我不是开发人员。当我在处理个人项目时,我只是将它们放在一起以解决JS中不存在的枚举的限制。
样品课
class Colors {
// Private Fields
static #_RED = 0;
static #_GREEN = 1;
static #_BLUE = 2;
// Accessors for "get" functions only (no "set" functions)
static get RED() { return this.#_RED; }
static get GREEN() { return this.#_GREEN; }
static get BLUE() { return this.#_BLUE; }
}
现在,您应该可以直接调用枚举了。
Colors.RED; // 0
Colors.GREEN; // 1
Colors.BLUE; // 2
使用私有字段和有限的访问器的结合意味着现有的枚举值得到了很好的保护(它们现在基本上是常量)。
Colors.RED = 10 // Colors.RED is still 0
Colors._RED = 10 // Colors.RED is still 0
Colors.#_RED = 10 // Colors.RED is still 0
我更喜欢@tonethar的方法,进行了一些增强和挖掘,以更好地了解ES6 / Node.js生态系统的基础。在隔离区服务器端具有背景的情况下,我更喜欢围绕平台基元使用功能样式的方法,这样可以最大程度地减少代码膨胀,以及由于引入新类型而导致进入死亡阴影的州管理谷地的滑坡,可读性-使解决方案和算法的意图更加清晰。
TDD,ES6,Node.js,Lodash,Jest,Babel,ESLint的解决方案
// ./utils.js
import _ from 'lodash';
const enumOf = (...args) =>
Object.freeze( Array.from( Object.assign(args) )
.filter( (item) => _.isString(item))
.map((item) => Object.freeze(Symbol.for(item))));
const sum = (a, b) => a + b;
export {enumOf, sum};
// ./utils.js
// ./kittens.js
import {enumOf} from "./utils";
const kittens = (()=> {
const Kittens = enumOf(null, undefined, 'max', 'joe', 13, -13, 'tabby', new
Date(), 'tom');
return () => Kittens;
})();
export default kittens();
// ./kittens.js
// ./utils.test.js
import _ from 'lodash';
import kittens from './kittens';
test('enum works as expected', () => {
kittens.forEach((kitten) => {
// in a typed world, do your type checks...
expect(_.isSymbol(kitten));
// no extraction of the wrapped string here ...
// toString is bound to the receiver's type
expect(kitten.toString().startsWith('Symbol(')).not.toBe(false);
expect(String(kitten).startsWith('Symbol(')).not.toBe(false);
expect(_.isFunction(Object.valueOf(kitten))).not.toBe(false);
const petGift = 0 === Math.random() % 2 ? kitten.description :
Symbol.keyFor(kitten);
expect(petGift.startsWith('Symbol(')).not.toBe(true);
console.log(`Unwrapped Christmas kitten pet gift '${petGift}', yeee :)
!!!`);
expect(()=> {kitten.description = 'fff';}).toThrow();
});
});
// ./utils.test.js
这是我的方法,包括一些辅助方法
export default class Enum {
constructor(name){
this.name = name;
}
static get values(){
return Object.values(this);
}
static forName(name){
for(var enumValue of this.values){
if(enumValue.name === name){
return enumValue;
}
}
throw new Error('Unknown value "' + name + '"');
}
toString(){
return this.name;
}
}
--
import Enum from './enum.js';
export default class ColumnType extends Enum {
constructor(name, clazz){
super(name);
this.associatedClass = clazz;
}
}
ColumnType.Integer = new ColumnType('Integer', Number);
ColumnType.Double = new ColumnType('Double', Number);
ColumnType.String = new ColumnType('String', String);
您可以使用ES6地图
const colors = new Map([
['RED', 'red'],
['BLUE', 'blue'],
['GREEN', 'green']
]);
console.log(colors.get('RED'));
文章标签:ecmascript-6 , enums , immutability , javascript , symbols
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!
评论已关闭!