ES6 перечисления только для чтения, которые могут сопоставлять значение с именем


Я хотел бы определить перечислительную структуру в JS, но у меня есть два требования:

  1. значения доступны только для чтения, то есть пользователи не могут их присваивать.
  2. значения (0, 1, 2,...) могут быть сопоставлены обратно в имена (как с Java name method )
Методы, которые я знаю, чтобы создать перечисления, как это, как правило, отвечают одному требованию или другому, а не обоим.

Я пробовал:

const MyEnum = {
  a: 0,
  b: 1,
  c: 2
};

Перечисление само по себе постоянно, но значения все еще изменчивы и я не могу эффективно сопоставлять значения с именами.

При написании enum в машинописном виде он выводит:

var MyEnum;
(function (MyEnum) {
    MyEnum[MyEnum["a"] = 0] = "a";
    MyEnum[MyEnum["b"] = 1] = "b";
    MyEnum[MyEnum["c"] = 2] = "c";
})(MyEnum || (MyEnum = {}));

Это может отображать оба пути, но все еще не имеет постоянных значений.

Единственный вариант, который я нашел, удовлетворяющий обоим требованиям, - это использование геттеров для класса:

class MyEnum {
  get a() {
    return 0;
  }
  ...
}
Этот метод резко ограничивает легальные имена и имеет много накладных расходов, особенно в браузерах, которые не очень хорошо (или не могут) встроить геттеры.

@- предположил Шмидти. замораживание объекта :

const MyEnum = Object.freeze({
  a: 0,
  b: 1,
  c: 2
});

Это хорошо соответствует постоянному требованию, но не обеспечивает отличный способ отображения значений обратно в имена.

Я мог бы написать помощник, который строит обратное отображение, например:

function reverseEnum(enum) {
  Object.keys(enum).forEach(k => {
    enum[enum[k]] = k;
  });
}
Но любой вид программного решения для генерации обратного отображения столкнется с проблемами, если исходный объект заморожен или иным образом фактически постоянен.

Есть ли чистое, краткое решение этого в JS?

4 17

4 ответа:

Я бы использовал карту, чтобы ваши значения перечисления могли быть любого типа, а не принуждать их к строкам.

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]);

Для обеспечения правильного использования значений перечисления. Используя только строку, вы не можете гарантировать, что какой-то код просто не передал обычную строку, которая случайно совпадает с одним из ваших значений перечисления. Если ваши значения всегда являются строками или символами, вы также можете поменять карту местами для простого объекта.

Это делает довольно хорошую работу, ИМХО.

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);
}

Совсем недавно реализована версия 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 вниз для совместимости с браузером.

То же самое, что и у Shmiddty, но немного более многоразовое благодаря Ramda, и немного красивее благодаря 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