Подтвердить что ты не робот

Перечисления только для чтения 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 чистое, сжатое решение?

4b9b3361

Ответ 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