Перечисления только для чтения ES6, которые могут отображать значение для имени
Я хотел бы определить структуру с перечислением в JS, но имеет два требования:
- Значения могут быть доступны только для чтения, т.е. пользователи не могут их назначать.
- Значения (0, 1, 2,...) могут быть отображены обратно в имена (как в Java метод имени)
Методы, которые я знаю для создания перечислений, подобных этому, обычно соответствуют одному требованию или другому, а не тем и другим.
Я пробовал:
const MyEnum = {
a: 0,
b: 1,
c: 2
};
Само перечисление является постоянным, но значения все еще изменяемы, и я не могу эффективно преобразовать значения обратно в имена.
При записи enum
в Typescript он выводит:
var MyEnum;
(function (MyEnum) {
MyEnum[MyEnum["a"] = 0] = "a";
MyEnum[MyEnum["b"] = 1] = "b";
MyEnum[MyEnum["c"] = 2] = "c";
})(MyEnum || (MyEnum = {}));
Это может отображаться в обоих направлениях, но по-прежнему не имеет постоянных значений.
Единственный вариант, который я нашел, который отвечает обоим требованиям, будет использовать getters в классе:
class MyEnum {
get a() {
return 0;
}
...
}
Этот метод значительно ограничивает юридические имена и имеет много накладных расходов, особенно в браузерах, которые не имеют встроенных геттеров (или не могут).
@Shmiddty предложил заморозить объект:
const MyEnum = Object.freeze({
a: 0,
b: 1,
c: 2
});
Это удовлетворяет постоянным требованиям, но не обеспечивает отличный способ сопоставления значений с именами.
Я мог бы написать помощник, который строит обратное отображение, например:
function reverseEnum(enum) {
Object.keys(enum).forEach(k => {
enum[enum[k]] = k;
});
}
Но любое программное решение для создания обратного сопоставления будет сталкиваться с проблемами, если исходный объект заморожен или фактически фактически постоянным.
Есть ли в JS чистое, сжатое решение?
Ответы
Ответ 1
Я бы использовал карту, чтобы ваши значения перечисления могли быть любого типа, а не принуждать их к строкам.
function Enum(obj){
const keysByValue = new Map();
const EnumLookup = value => keysByValue.get(value);
for (const key of Object.keys(obj)){
EnumLookup[key] = obj[key];
keysByValue.set(EnumLookup[key], key);
}
// Return a function with all your enum properties attached.
// Calling the function with the value will return the key.
return Object.freeze(EnumLookup);
}
Если ваше перечисление - все строки, я, вероятно, также изменил бы одну строку:
EnumLookup[key] = Symbol(obj[key]);
чтобы обеспечить правильное использование значений перечисления. Используя только строку, у вас нет гарантии, что какой-то код не просто передал обычную строку, которая бывает такой же, как и одно из ваших значений перечисления. Если ваши значения всегда являются строками или символами, вы также можете поменять карту на простой объект.
Ответ 2
Это делает довольно хорошую работу, ИМХО.
function Enum(a){
let i = Object
.keys(a)
.reduce((o,k)=>(o[a[k]]=k,o),{});
return Object.freeze(
Object.keys(a).reduce(
(o,k)=>(o[k]=a[k],o), v=>i[v]
)
);
} // y u so terse?
const FOO = Enum({
a: 0,
b: 1,
c: "banana"
});
console.log(FOO.a, FOO.b, FOO.c); // 0 1 banana
console.log(FOO(0), FOO(1), FOO("banana")); // a b c
try {
FOO.a = "nope";
}
catch (e){
console.log(e);
}
Ответ 3
Совсем недавно реализована версия Es6, которая работает достаточно хорошо:
const k_VALUES = {}
export class ErrorCode {
constructor(p_apiCode, p_httpCode){
this.apiCode = p_apiCode;
this.httpCode = p_httpCode;
k_VALUES[p_apiCode] = this;
}
static create(p_apiCode){
if(k_VALUES[p_apiCode]){
return k_VALUES[p_apiCode];
}
return ErrorCode.UNKNOWN;
}
}
ErrorCode.UNKNOWN = new ErrorCode(0, 500);
ErrorCode.NOT_FOUND = new ErrorCode(-1000, 404);
ErrorCode.NOT_FOUND_EMAIL = new ErrorCode(-1001, 404);
ErrorCode.BAD_REQUEST = new ErrorCode(-1010, 404);
Я хотел реализовать аналогичный шаблон, как то, что мы делаем с перечислениями Java. Это позволяет мне использовать конструктор для передачи значений. Конструктор затем замораживает объект ErrorCode - приятный и удобный.
Использование: сначала импортируйте свой класс enum...
import {ErrorCode} from "../common/services/errors/ErrorCode";
Теперь, после импорта класса enum, выполните его следующим образом:
if( errCode.includes(ErrorCode.BAD_REQUEST.apiCode) ){...}
PS > Это используется в сочетании с настройкой Webpack с помощью Babel для преобразования наших классов ES6 для совместимости браузеров.
Ответ 4
То же, что дано Шмиддти, но немного более многоразовое благодаря Рамде и немного красивее благодаря coffeescript
R = require('ramda')
lib = {}
lib.reduceKeys = (obj) -> R.reduce R.__, R.__, R.keys obj
lib.swapKeys = R.curry (obj, acc) ->
(lib.reduceKeys obj) ((acc, key) ->
acc[obj[key]] = key
acc
), acc
lib.copy = R.curry (obj, acc) ->
(lib.reduceKeys obj) ((acc, key) ->
acc[key] = obj[key]
acc
), acc
lib.Enum = (obj) ->
swapped = lib.swapKeys obj, {}
Object.freeze lib.copy obj, (val) -> swapped[val]
exports[key] = val for own key, val of lib