javascript - node - vue enum




Какой синтаксис является предпочтительным для определения перечислений в JavaScript? (20)

Используйте Javascript Proxies

TLDR: добавьте этот класс в свои служебные методы и используйте его во всем коде, он имитирует поведение Enum из традиционных языков программирования и фактически выдает ошибки, когда вы пытаетесь получить доступ к не существующему перечислителю или добавить / обновить перечислитель. Нет необходимости полагаться на Object.freeze() .

class Enum {
  constructor(enumObj) {
    const handler = {
      get(target, name) {
        if (typeof target[name] != 'undefined') {
          return target[name];
        }
        throw new Error(`No such enumerator: ${name}`);
      },
      set() {
        throw new Error('Cannot add/update properties on an Enum instance after it is defined')
      }
    };

    return new Proxy(enumObj, handler);
  }
}

Затем создайте перечисления, создав экземпляр класса:

const roles = new Enum({
  ADMIN: 'Admin',
  USER: 'User',
});

Полное объяснение:

Одна очень полезная особенность Enums, которую вы получаете от традиционных языков, состоит в том, что они взрываются (выдают ошибку времени компиляции), если вы пытаетесь получить доступ к перечислителю, который не существует.

Помимо замораживания поддельной структуры enum для предотвращения случайного / злонамеренного добавления дополнительных значений, ни один из других ответов не затрагивает эту внутреннюю особенность Enums.

Как вы, вероятно, знаете, доступ к несуществующим элементам в JavaScript просто возвращает undefined и не разрушает ваш код. Поскольку перечислители являются предопределенными константами (т.е. днями недели), никогда не должно быть случая, когда перечислитель должен быть неопределенным.

Не поймите меня неправильно, поведение JavaScript при возврате undefined при доступе к неопределенным свойствам на самом деле является очень мощной функцией языка, но это не та функция, которая вам нужна, когда вы пытаетесь смоделировать традиционные структуры Enum.

Здесь прокси объекты сияют. Прокси были стандартизированы в языке с введением ES6 (ES2015). Вот описание из MDN:

Объект Proxy используется для определения пользовательского поведения для основных операций (например, поиск свойства, присваивание, перечисление, вызов функции и т. Д.).

Подобно прокси-серверу веб-сервера, прокси-серверы JavaScript способны перехватывать операции над объектами (с использованием «ловушек», называть их хуками, если хотите) и позволяют выполнять различные проверки, действия и / или манипуляции до их завершения (или в некоторых случаях вообще прекращение операций, что мы и хотим делать, если и когда мы пытаемся сослаться на перечислитель, который не существует).

Вот надуманный пример, который использует объект Proxy для имитации Enums. В этом примере перечислители являются стандартными методами HTTP (например, «GET», «POST» и т. Д.):

// Class for creating enums (13 lines)
// Feel free to add this to your utility library in 
// your codebase and profit! Note: As Proxies are an ES6 
// feature, some browsers/clients may not support it and 
// you may need to transpile using a service like babel

class Enum {
  // The Enum class instantiates a JavaScript Proxy object.
  // Instantiating a `Proxy` object requires two parameters, 
  // a `target` object and a `handler`. We first define the handler,
  // then use the handler to instantiate a Proxy.

  // A proxy handler is simply an object whose properties
  // are functions which define the behavior of the proxy 
  // when an operation is performed on it. 
  
  // For enums, we need to define behavior that lets us check what enumerator
  // is being accessed and what enumerator is being set. This can be done by 
  // defining "get" and "set" traps.
  constructor(enumObj) {
    const handler = {
      get(target, name) {
        if (typeof target[name] != 'undefined') {
          return target[name]
        }
        throw new Error(`No such enumerator: ${name}`)
      },
      set() {
        throw new Error('Cannot add/update properties on an Enum instance after it is defined')
      }
    }


    // Freeze the target object to prevent modifications
    return new Proxy(enumObj, handler)
  }
}


// Now that we have a generic way of creating Enums, lets create our first Enum!
const httpMethods = new Enum({
  DELETE: "DELETE",
  GET: "GET",
  OPTIONS: "OPTIONS",
  PATCH: "PATCH",
  POST: "POST",
  PUT: "PUT"
})

// Sanity checks
console.log(httpMethods.DELETE)
// logs "DELETE"

try {
  httpMethods.delete = "delete"
} catch (e) {
console.log("Error: ", e.message)
}
// throws "Cannot add/update properties on an Enum instance after it is defined"

try {
  console.log(httpMethods.delete)
} catch (e) {
  console.log("Error: ", e.message)
}
// throws "No such enumerator: delete"

ASIDE: Какого черта это прокси?

Я помню, когда я впервые начал видеть слово «прокси» везде, это определенно не имело смысла для меня долгое время. Если это вы сейчас, я думаю, что простой способ обобщить прокси - это думать о них как о программном обеспечении, учреждениях или даже людях, которые выступают в качестве посредников или посредников между двумя серверами, компаниями или людьми.

Какой синтаксис является предпочтительным для определения перечислений в JavaScript? Что-то вроде:

my.namespace.ColorEnum = {
    RED : 0,
    GREEN : 1,
    BLUE : 2
}

// later on

if(currentColor == my.namespace.ColorEnum.RED) {
   // whatever
}

Или есть более предпочтительная идиома?


В большинстве современных браузеров существует тип данных symbol примитива, который можно использовать для создания перечисления. Это обеспечит безопасность типов перечисления, так как каждое значение символа гарантировано JavaScript уникальным, то есть Symbol() != Symbol() . Например:

const COLOR = Object.freeze({RED: Symbol(), BLUE: Symbol()});

Чтобы упростить отладку, вы можете добавить описание к перечисляемым значениям:

const COLOR = Object.freeze({RED: Symbol("RED"), BLUE: Symbol("BLUE")});

Plunker demo

На GitHub вы можете найти оболочку, которая упрощает код, необходимый для инициализации enum:

const color = new Enum("RED", "BLUE")

color.RED.toString() // Symbol(RED)
color.getName(color.RED) // RED
color.size // 2
color.values() // Symbol(RED), Symbol(BLUE)
color.toString() // RED,BLUE

Итог: вы не можете.

Вы можете подделать это, но вы не получите безопасность типа. Обычно это делается путем создания простого словаря строковых значений, сопоставленных с целочисленными значениями. Например:

var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}

Document.Write("Enumerant: " + DaysEnum.tuesday);

Проблема с этим подходом? Вы можете случайно переопределить свой перечислитель или случайно иметь повторяющиеся значения перечислителя. Например:

DaysEnum.monday = 4; // whoops, monday is now thursday, too

редактировать

А как насчет Object.freeze Артура Цайка? Разве это не сработает, чтобы помешать вам установить понедельник на четверг? - Фрай Квад

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

Теперь .... теперь это открывает некоторые очень интересные возможности.

Редактировать 2
Вот очень хорошая библиотека для создания перечислений.

http://www.2ality.com/2011/10/enums.html

Хотя это, вероятно, не подходит для каждого допустимого использования перечислений, оно проходит очень долгий путь.


Начиная с 1.8.5, можно запечатать и заморозить объект, поэтому определите вышеизложенное как:

var DaysEnum = Object.freeze({"monday":1, "tuesday":2, "wednesday":3, ...})

или же

var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Object.freeze(DaysEnum)

и вуаля! JS перечисления.

Примечание: я написал это в 2011 году, но это 2019 год - используйте const чтобы предотвратить перезапись вашего словаря enum.

Тем не менее, это не мешает вам присвоить нежелательное значение переменной, что часто является главной целью перечислений:

let day = DaysEnum.tuesday
day = 298832342 // goes through without any errors

Один из способов обеспечить более высокую степень безопасности типов (с помощью перечислений или иным образом) - использовать такой инструмент, как TypeScript или Flow .

Source

Цитаты не нужны, но я сохранил их для согласованности.


Это решение, которое я использую.

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

Я обычно использую последние 2 метода для отображения перечислений из объектов сообщений.

Некоторые преимущества этого подхода:

  • Легко объявлять перечисления
  • Легко получить доступ к вашим перечислениям
  • Ваши перечисления могут быть сложными типами
  • Класс Enum имеет некоторое ассоциативное кэширование, если вы часто используете getByValue

Некоторые недостатки:

  • Там происходит некоторое грязное управление памятью, так как я храню ссылки на перечисления
  • Все еще нет типа безопасности

Это старая версия, которую я знаю, но с тех пор она была реализована через интерфейс TypeScript:

var MyEnum;
(function (MyEnum) {
    MyEnum[MyEnum["Foo"] = 0] = "Foo";
    MyEnum[MyEnum["FooBar"] = 2] = "FooBar";
    MyEnum[MyEnum["Bar"] = 1] = "Bar";
})(MyEnum|| (MyEnum= {}));

Это позволяет вам искать как MyEnum.Bar который возвращает 1, так и MyEnum[1] который возвращает «Bar» независимо от порядка объявления.


«Предпочтительный синтаксис» большинства людей уже был перечислен выше. Однако существует главная общая проблема: размер кода. Все остальные ответы, перечисленные здесь, увеличивают размер вашего кода до крайности. Для обеспечения максимальной производительности, читабельности кода, управления крупномасштабными проектами, подсказок синтаксиса во многих редакторах кода и уменьшения размера кода путем минимизации это правильный способ выполнения перечислений.

const ENUM_COLORENUM_RED   = 0,
      ENUM_COLORENUM_GREEN = 1,
      ENUM_COLORENUM_BLUE  = 2,
      ENUMLEN_COLORENUM    = 3;

// later on

if(currentColor === ENUM_COLORENUM_RED) {
   // whatever
}

Кроме того, этот синтаксис допускает ясное и краткое расширение класса, как показано ниже.

(Длина: 2450 байт)

(function(window){
    "use strict";
    var parseInt = window.parseInt

    const ENUM_PIXELCOLOR_TYPE = 0, // is a ENUM_PIXELTYPE
          ENUMLEN_PIXELCOLOR   = 1,
          ENUM_SOLIDCOLOR_R    = ENUMLEN_PIXELCOLOR+0,
          ENUM_SOLIDCOLOR_G    = ENUMLEN_PIXELCOLOR+1,
          ENUM_SOLIDCOLOR_B    = ENUMLEN_PIXELCOLOR+2,
          ENUMLEN_SOLIDCOLOR   = ENUMLEN_PIXELCOLOR+3,
          ENUM_ALPHACOLOR_R    = ENUMLEN_PIXELCOLOR+0,
          ENUM_ALPHACOLOR_G    = ENUMLEN_PIXELCOLOR+1,
          ENUM_ALPHACOLOR_B    = ENUMLEN_PIXELCOLOR+2,
          ENUM_ALPHACOLOR_A    = ENUMLEN_PIXELCOLOR+3,
          ENUMLEN_ALPHACOLOR   = ENUMLEN_PIXELCOLOR+4,
          ENUM_PIXELTYPE_SOLID = 0,
          ENUM_PIXELTYPE_ALPHA = 1,
          ENUM_PIXELTYPE_UNKNOWN = 2,
          ENUMLEN_PIXELTYPE    = 2;

    function parseHexColor(inputString) {
        var rawstr = inputString.trim().substring(1);
        var result = [];
        if (rawstr.length === 8) {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
            result[ENUM_ALPHACOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[ENUM_ALPHACOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[ENUM_ALPHACOLOR_B] = parseInt(rawstr.substring(4,6), 16);
            result[ENUM_ALPHACOLOR_A] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 4) {
            result[ENUM_ALPHACOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[ENUM_ALPHACOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[ENUM_ALPHACOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
            result[ENUM_ALPHACOLOR_A] = parseInt(rawstr[3], 16) * 0x11;
        } else if (rawstr.length === 6) {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[ENUM_SOLIDCOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[ENUM_SOLIDCOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[ENUM_SOLIDCOLOR_B] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 3) {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[ENUM_SOLIDCOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[ENUM_SOLIDCOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[ENUM_SOLIDCOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
        } else {
            result[ENUM_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_UNKNOWN;
        }
        return result;
    }

    // the red component of green
    console.log(parseHexColor("#0f0")[ENUM_SOLIDCOLOR_R]);
    // the alpha of transparent purple
    console.log(parseHexColor("#f0f7")[ENUM_ALPHACOLOR_A]); 
    // the enumerated array for turquoise
    console.log(parseHexColor("#40E0D0"));
})(self);

Некоторые могут сказать, что это менее практично, чем другие решения: оно занимает много места, требует много времени для написания и не содержит синтаксиса сахара. Эти люди были бы правы, если бы они не минимизировали свой код. Тем не менее, ни один разумный человек не оставит единый код в конечном продукте. Для этого миниатюры Closure Compiler - лучшее, что я пока не нашел. Онлайн доступ можно найти here . Компилятор Closure может взять все эти перечисленные данные и вставить их в строку, что делает ваш Javascript очень маленьким и быстро запускаемым. Обратите внимание.

(Длина: 605 байт)

'use strict';(function(e){function d(a){a=a.trim().substring(1);var b=[];8===a.length?(b[0]=1,b[1]=c(a.substring(0,2),16),b[2]=c(a.substring(2,4),16),b[3]=c(a.substring(4,6),16),b[4]=c(a.substring(4,6),16)):4===a.length?(b[1]=17*c(a[0],16),b[2]=17*c(a[1],16),b[3]=17*c(a[2],16),b[4]=17*c(a[3],16)):6===a.length?(b[0]=0,b[1]=c(a.substring(0,2),16),b[2]=c(a.substring(2,4),16),b[3]=c(a.substring(4,6),16)):3===a.length?(b[0]=0,b[1]=17*c(a[0],16),b[2]=17*c(a[1],16),b[3]=17*c(a[2],16)):b[0]=2;return b}var c=
e.parseInt;console.log(d("#0f0")[1]);console.log(d("#f0f7")[4]);console.log(d("#40E0D0"))})(self);

Теперь давайте посмотрим, насколько большим будет эквивалентный файл без каких-либо перечислений.

Исходный код без использования перечислений (длина: 1 973 байта (на 477 байтов короче перечисляемого кода!))
Сокращено без использования перечислений (длина: 843 байта (на 238 байтов больше, чем в перечисляемом коде ))

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

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

// JG = Jack Giffin
const ENUM_JG_COLORENUM_RED   = 0,
      ENUM_JG_COLORENUM_GREEN = 1,
      ENUM_JG_COLORENUM_BLUE  = 2,
      ENUMLEN_JG_COLORENUM    = 3;

// later on

if(currentColor === ENUM_JG_COLORENUM_RED) {
   // whatever
}

// PL = Pepper Loftus
// BK = Bob Knight
const ENUM_PL_ARRAYTYPE_UNSORTED   = 0,
      ENUM_PL_ARRAYTYPE_ISSORTED   = 1,
      ENUM_BK_ARRAYTYPE_CHUNKED    = 2, // added by Bob Knight
      ENUM_JG_ARRAYTYPE_INCOMPLETE = 3, // added by jack giffin
      ENUMLEN_PL_COLORENUM         = 4;

// later on

if(
  randomArray === ENUM_PL_ARRAYTYPE_UNSORTED ||
  randomArray === ENUM_BK_ARRAYTYPE_CHUNKED
) {
   // whatever
}

Кроме того, эта форма перечисления также намного быстрее после минификации. В обычных именованных свойствах браузер должен использовать хеш-карты для поиска того, где находится свойство на объекте. Хотя JIST-компиляторы интеллектуально кэшируют это местоположение на объекте, для особых случаев, таких как удаление более низкого свойства из объекта, все еще остаются огромные накладные расходы. Но при использовании непрерывных не разреженных массивов PACKED_ELEMENTS целыми индексами браузер может пропустить большую часть этих издержек, поскольку индекс значения во внутреннем массиве уже указан. Да, согласно стандарту ECMAScript все свойства должны рассматриваться как строки. Тем не менее, этот аспект стандарта ECMAScript очень вводит в заблуждение относительно производительности, поскольку все браузеры имеют специальные оптимизации для числовых индексов в массивах.

Кроме того, моя личная вишня на вершине использует эту форму перечислений вместе с текстовым редактором CodeMirror в режиме Javascript. Режим подсветки синтаксиса Javascript в CodeMirror выделяет локальные переменные в текущей области видимости. Таким образом, вы сразу узнаете, когда правильно вводите имя переменной, потому что, если имя переменной было ранее объявлено с ключевым словом var , тогда имя переменной приобретает особый цвет (по умолчанию голубой). Даже если вы не используете CodeMirror, то по крайней мере браузер выдает полезное [variable name] is not defined исключение при выполнении кода с ошибочными именами перечислений. Кроме того, инструменты JavaScript, такие как JSLint и Closure Compiler, очень громко рассказывают вам, когда вы вводите неверный тип в имени переменной перечисления. CodeMirror, браузер и различные инструменты Javascript вместе делают отладку этой формы перечисления очень простой и действительно простой.

Таким образом, я прихожу к выводу, что эта форма перечисления действительно является способом не только минимизированного размера кода, но и производительности, ясности и совместной работы.


ОБНОВЛЕНИЕ : Спасибо за все отзывы, но я не думаю, что мой ответ ниже - лучший способ написать перечисления в Javascript. Смотрите мой блог для более подробной информации: Перечисления в Javascript .

Оповещение имени уже возможно:

if (currentColor == my.namespace.ColorEnum.RED) {
   // alert name of currentColor (RED: 0)
   var col = my.namespace.ColorEnum;
   for (var name in col) {
     if (col[name] == col.RED)
       alert(name);
   }
}

В качестве альтернативы, вы можете сделать объекты значений, чтобы вы могли получить торт и съесть его тоже:

var SIZE = {
  SMALL : {value: 0, name: "Small", code: "S"}, 
  MEDIUM: {value: 1, name: "Medium", code: "M"}, 
  LARGE : {value: 2, name: "Large", code: "L"}
};

var currentSize = SIZE.MEDIUM;
if (currentSize == SIZE.MEDIUM) {
  // this alerts: "1: Medium"
  alert(currentSize.value + ": " + currentSize.name);
}

В Javascript, поскольку это динамический язык, можно даже добавить значения enum в набор позже:

// Add EXTRALARGE size
SIZE.EXTRALARGE = {value: 3, name: "Extra Large", code: "XL"};

Помните, что поля перечисления (значение, имя и код в этом примере) не нужны для проверки идентичности и существуют только для удобства. Кроме того, имя свойства size не обязательно должно быть жестко закодировано, но также может быть установлено динамически. Итак, предположим, что вы знаете только имя для нового значения перечисления, вы все равно можете добавить его без проблем:

// Add 'Extra Large' size, only knowing it's name
var name = "Extra Large";
SIZE[name] = {value: -1, name: name, code: "?"};

Конечно, это означает, что некоторые предположения больше не могут быть сделаны (например, это значение представляет правильный порядок для размера).

Помните, в Javascript объект похож на карту или хеш-таблицу. Набор пар имя-значение. Вы можете просматривать их или иным образом манипулировать ими, не зная заранее о них.

НАПРИМЕР:

for (var sz in SIZE) {
  // sz will be the names of the objects in SIZE, so
  // 'SMALL', 'MEDIUM', 'LARGE', 'EXTRALARGE'
  var size = SIZE[sz]; // Get the object mapped to the name in sz
  for (var prop in size) {
    // Get all the properties of the size object, iterates over
    // 'value', 'name' and 'code'. You can inspect everything this way.        
  }
} 

И, между прочим, если вы заинтересованы в пространствах имен, вы можете взглянуть на мое решение для простого, но мощного управления пространством имен и зависимостей для javascript: Пакеты JS


Быстрый и простой способ будет:

var Colors = function(){
return {
    'WHITE':0,
    'BLACK':1,
    'RED':2,
    'GREEN':3
    }
}();

console.log(Colors.WHITE)  //this prints out "0"

Вы можете сделать что-то вроде этого

    var Enum = (function(foo) {

    var EnumItem = function(item){
        if(typeof item == "string"){
            this.name = item;
        } else {
            this.name = item.name;
        }
    }
    EnumItem.prototype = new String("DEFAULT");
    EnumItem.prototype.toString = function(){
        return this.name;
    }
    EnumItem.prototype.equals = function(item){
        if(typeof item == "string"){
            return this.name == item;
        } else {
            return this == item && this.name == item.name;
        }
    }

    function Enum() {
        this.add.apply(this, arguments);
        Object.freeze(this);
    }
    Enum.prototype.add = function() {
        for (var i in arguments) {
            var enumItem = new EnumItem(arguments[i]);
            this[enumItem.name] = enumItem;
        }
    };
    Enum.prototype.toList = function() {
        return Object.keys(this);
    };
    foo.Enum = Enum;
    return Enum;
})(this);
var STATUS = new Enum("CLOSED","PENDING", { name : "CONFIRMED", ackd : true });
var STATE = new Enum("CLOSED","PENDING","CONFIRMED",{ name : "STARTED"},{ name : "PROCESSING"});

Как определено в этой библиотеке. https://github.com/webmodule/foo/blob/master/foo.js#L217

Полный пример https://gist.github.com/lnt/bb13a2fd63cdb8bce85fd62965a20026


Я изменил решение Андре '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 buildSet = function(array) {
  var set = {};
  for (var i in array) {
    var item = array[i];
    set[item] = item;
  }
  return set;
}

var myEnum = buildSet(['RED','GREEN','BLUE']);
// myEnum.RED == 'RED' ...etc

В ES7 вы можете сделать элегантный ENUM, опираясь на статические атрибуты:

class ColorEnum  {
    static RED = 0 ;
    static GREEN = 1;
    static BLUE = 2;
}

затем

if (currentColor === ColorEnum.GREEN ) {/*-- coding --*/}

Преимущество (использование класса вместо буквального объекта) должен иметь родительский класс , Enumто все ваши перечисления будут распространяется этот класс.

 class ColorEnum  extends Enum {/*....*/}

Вот как Typescript переводит это enumв Javascript:

var makeEnum = function(obj) {
    obj[ obj['Active'] = 1 ] = 'Active';
    obj[ obj['Closed'] = 2 ] = 'Closed';
    obj[ obj['Deleted'] = 3 ] = 'Deleted';
}

Сейчас:

makeEnum( NewObj = {} )
// => {1: "Active", 2: "Closed", 3: "Deleted", Active: 1, Closed: 2, Deleted: 3}

Сначала я был озадачен, почему obj[1]возвращается 'Active', но потом понял, что его просто - оператор присваивания присваивает значение, а затем возвращает его

obj['foo'] = 1
// => 1

Даже если только статические методы (а не статические свойства) поддерживаются в ES2015 (см here также, §15.2.2.2), как ни странно , вы можете использовать ниже с Вавилонской с es2015предустановки:

class CellState {
    v: string;
    constructor(v: string) {
        this.v = v;
        Object.freeze(this);
    }
    static EMPTY       = new CellState('e');
    static OCCUPIED    = new CellState('o');
    static HIGHLIGHTED = new CellState('h');
    static values      = function(): Array<CellState> {
        const rv = [];
        rv.push(CellState.EMPTY);
        rv.push(CellState.OCCUPIED);
        rv.push(CellState.HIGHLIGHTED);
        return rv;
    }
}
Object.freeze(CellState);

Я обнаружил, что это работает должным образом даже между модулями (например, импорт CellStateenum из другого модуля), а также при импорте модуля с помощью Webpack.

Преимущество этого метода перед большинством других ответов заключается в том, что вы можете использовать его вместе со средством проверки статического типа (например, Flow ), и вы можете утверждать, что во время разработки, используя проверку статического типа, что ваши переменные, параметры и т. Д. Имеют определенную CellState" enum ", а не какой-либо другой enum (который было бы невозможно отличить, если бы вы использовали общие объекты или символы).

Обновить

Вышеприведенный код имеет недостаток в том, что он позволяет создавать дополнительные объекты типа CellState(даже если вы не можете назначить их статическим полям, CellStateтак как он заморожен). Тем не менее, приведенный ниже более усовершенствованный код предлагает следующие преимущества:

  1. больше CellStateнельзя создавать объекты типа
  2. вам гарантировано, что никаким двум экземплярам enum не присвоен одинаковый код
  3. служебный метод для получения перечисления из строкового представления
  4. valuesфункция , которая возвращает все экземпляры перечисления не должны создавать возвращаемое значение в приведенном выше, ручной (и подверженной ошибкам) способом.

    'use strict';
    
    class Status {
    
    constructor(code, displayName = code) {
        if (Status.INSTANCES.has(code))
            throw new Error(`duplicate code value: [${code}]`);
        if (!Status.canCreateMoreInstances)
            throw new Error(`attempt to call constructor(${code}`+
           `, ${displayName}) after all static instances have been created`);
        this.code        = code;
        this.displayName = displayName;
        Object.freeze(this);
        Status.INSTANCES.set(this.code, this);
    }
    
    toString() {
        return `[code: ${this.code}, displayName: ${this.displayName}]`;
    }
    static INSTANCES   = new Map();
    static canCreateMoreInstances      = true;
    
    // the values:
    static ARCHIVED    = new Status('Archived');
    static OBSERVED    = new Status('Observed');
    static SCHEDULED   = new Status('Scheduled');
    static UNOBSERVED  = new Status('Unobserved');
    static UNTRIGGERED = new Status('Untriggered');
    
    static values      = function() {
        return Array.from(Status.INSTANCES.values());
    }
    
    static fromCode(code) {
        if (!Status.INSTANCES.has(code))
            throw new Error(`unknown code: ${code}`);
        else
            return Status.INSTANCES.get(code);
    }
    }
    
    Status.canCreateMoreInstances = false;
    Object.freeze(Status);
    exports.Status = Status;

Если вы используете Backbone , вы можете получить полную функциональность перечисления (поиск по идентификатору, имени, пользовательским членам) бесплатно с помощью Backbone.Collection .

// enum instance members, optional
var Color = Backbone.Model.extend({
    print : function() {
        console.log("I am " + this.get("name"))
    }
});

// enum creation
var Colors = new Backbone.Collection([
    { id : 1, name : "Red", rgb : 0xFF0000},
    { id : 2, name : "Green" , rgb : 0x00FF00},
    { id : 3, name : "Blue" , rgb : 0x0000FF}
], {
    model : Color
});

// Expose members through public fields.
Colors.each(function(color) {
    Colors[color.get("name")] = color;
});

// using
Colors.Red.print()

Это легко использовать, я думаю. https://.com/a/32245370/4365315

var A = {a:11, b:22}, 
enumA = new TypeHelper(A);

if(enumA.Value === A.b || enumA.Key === "a"){ 
... 
}

var keys = enumA.getAsList();//[object, object]

//set
enumA.setType(22, false);//setType(val, isKey)

enumA.setType("a", true);

enumA.setTypeByIndex(1);

ОБНОВИТЬ:

Есть мои вспомогательные коды ( TypeHelper).

var Helper = {
    isEmpty: function (obj) {
        return !obj || obj === null || obj === undefined || Array.isArray(obj) && obj.length === 0;
    },

    isObject: function (obj) {
        return (typeof obj === 'object');
    },

    sortObjectKeys: function (object) {
        return Object.keys(object)
            .sort(function (a, b) {
                c = a - b;
                return c
            });
    },
    containsItem: function (arr, item) {
        if (arr && Array.isArray(arr)) {
            return arr.indexOf(item) > -1;
        } else {
            return arr === item;
        }
    },

    pushArray: function (arr1, arr2) {
        if (arr1 && arr2 && Array.isArray(arr1)) {
            arr1.push.apply(arr1, Array.isArray(arr2) ? arr2 : [arr2]);
        }
    }
};
function TypeHelper() {
    var _types = arguments[0],
        _defTypeIndex = 0,
        _currentType,
        _value,
        _allKeys = Helper.sortObjectKeys(_types);

    if (arguments.length == 2) {
        _defTypeIndex = arguments[1];
    }

    Object.defineProperties(this, {
        Key: {
            get: function () {
                return _currentType;
            },
            set: function (val) {
                _currentType.setType(val, true);
            },
            enumerable: true
        },
        Value: {
            get: function () {
                return _types[_currentType];
            },
            set: function (val) {
                _value.setType(val, false);
            },
            enumerable: true
        }
    });
    this.getAsList = function (keys) {
        var list = [];
        _allKeys.forEach(function (key, idx, array) {
            if (key && _types[key]) {

                if (!Helper.isEmpty(keys) && Helper.containsItem(keys, key) || Helper.isEmpty(keys)) {
                    var json = {};
                    json.Key = key;
                    json.Value = _types[key];
                    Helper.pushArray(list, json);
                }
            }
        });
        return list;
    };

    this.setType = function (value, isKey) {
        if (!Helper.isEmpty(value)) {
            Object.keys(_types).forEach(function (key, idx, array) {
                if (Helper.isObject(value)) {
                    if (value && value.Key == key) {
                        _currentType = key;
                    }
                } else if (isKey) {
                    if (value && value.toString() == key.toString()) {
                        _currentType = key;
                    }
                } else if (value && value.toString() == _types[key]) {
                    _currentType = key;
                }
            });
        } else {
            this.setDefaultType();
        }
        return isKey ? _types[_currentType] : _currentType;
    };

    this.setTypeByIndex = function (index) {
        for (var i = 0; i < _allKeys.length; i++) {
            if (index === i) {
                _currentType = _allKeys[index];
                break;
            }
        }
    };

    this.setDefaultType = function () {
        this.setTypeByIndex(_defTypeIndex);
    };

    this.setDefaultType();
}

var TypeA = {
    "-1": "Any",
    "2": "2L",
    "100": "100L",
    "200": "200L",
    "1000": "1000L"
};

var enumA = new TypeHelper(TypeA, 4);

document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");


enumA.setType("200L", false);
document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");

enumA.setDefaultType();
document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");


enumA.setTypeByIndex(1);
document.writeln("Key = ", enumA.Key,", Value = ", enumA.Value, "<br>");

document.writeln("is equals = ", (enumA.Value == TypeA["2"]));


Я написал enumerationjs на очень маленькие библиотеки для решения этой проблемы , которая обеспечивает безопасность типов , позволяют перечисляемые константы наследовать от прототипа , гарантирует перечисляемые константы и типы перечислений быть неизменные + много маленьких функций. Это позволяет проводить рефакторинг большого количества кода и перемещать некоторую логику в определение перечисления. Вот пример:

var CloseEventCodes = new Enumeration("closeEventCodes", {
  CLOSE_NORMAL:          { _id: 1000, info: "Connection closed normally" },
  CLOSE_GOING_AWAY:      { _id: 1001, info: "Connection closed going away" },
  CLOSE_PROTOCOL_ERROR:  { _id: 1002, info: "Connection closed due to protocol error"  },
  CLOSE_UNSUPPORTED:     { _id: 1003, info: "Connection closed due to unsupported operation" },
  CLOSE_NO_STATUS:       { _id: 1005, info: "Connection closed with no status" },
  CLOSE_ABNORMAL:        { _id: 1006, info: "Connection closed abnormally" },
  CLOSE_TOO_LARGE:       { _id: 1009, info: "Connection closed due to too large packet" }
},{ talk: function(){
    console.log(this.info); 
  }
});


CloseEventCodes.CLOSE_TOO_LARGE.talk(); //prints "Connection closed due to too large packet"
CloseEventCodes.CLOSE_TOO_LARGE instanceof CloseEventCodes //evaluates to true

Enumeration это в основном завод.

Полностью документированное руководство доступно здесь. Надеюсь это поможет.


Я создал класс Enum, который может извлекать значения и имена в O (1). Он также может генерировать массив объектов, содержащий все имена и значения.

function Enum(obj) {
    // Names must be unique, Values do not.
    // Putting same values for different Names is risky for this implementation

    this._reserved = {
        _namesObj: {},
        _objArr: [],
        _namesArr: [],
        _valuesArr: [],
        _selectOptionsHTML: ""
    };

    for (k in obj) {
        if (obj.hasOwnProperty(k)) {
            this[k] = obj[k];
            this._reserved._namesObj[obj[k]] = k;
        }
    }
}
(function () {
    this.GetName = function (val) {
        if (typeof this._reserved._namesObj[val] === "undefined")
            return null;
        return this._reserved._namesObj[val];
    };

    this.GetValue = function (name) {
        if (typeof this[name] === "undefined")
            return null;
        return this[name];
    };

    this.GetObjArr = function () {
        if (this._reserved._objArr.length == 0) {
            var arr = [];
            for (k in this) {
                if (this.hasOwnProperty(k))
                    if (k != "_reserved")
                        arr.push({
                            Name: k,
                            Value: this[k]
                        });
            }
            this._reserved._objArr = arr;
        }
        return this._reserved._objArr;
    };

    this.GetNamesArr = function () {
        if (this._reserved._namesArr.length == 0) {
            var arr = [];
            for (k in this) {
                if (this.hasOwnProperty(k))
                    if (k != "_reserved")
                        arr.push(k);
            }
            this._reserved._namesArr = arr;
        }
        return this._reserved._namesArr;
    };

    this.GetValuesArr = function () {
        if (this._reserved._valuesArr.length == 0) {
            var arr = [];
            for (k in this) {
                if (this.hasOwnProperty(k))
                    if (k != "_reserved")
                        arr.push(this[k]);
            }
            this._reserved._valuesArr = arr;
        }
        return this._reserved._valuesArr;
    };

    this.GetSelectOptionsHTML = function () {
        if (this._reserved._selectOptionsHTML.length == 0) {
            var html = "";
            for (k in this) {
                if (this.hasOwnProperty(k))
                    if (k != "_reserved")
                        html += "<option value='" + this[k] + "'>" + k + "</option>";
            }
            this._reserved._selectOptionsHTML = html;
        }
        return this._reserved._selectOptionsHTML;
    };
}).call(Enum.prototype);

Вы можете init'd это так:

var enum1 = new Enum({
    item1: 0,
    item2: 1,
    item3: 2
});

Чтобы получить значение (как Enums в C #):

var val2 = enum1.item2;

Чтобы получить имя для значения (может быть неоднозначным, если поставить одно и то же значение для разных имен):

var name1 = enum1.GetName(0);  // "item1"

Чтобы получить массив с каждым именем и значением в объекте:

var arr = enum1.GetObjArr();

Будет генерировать:

[{ Name: "item1", Value: 0}, { ... }, ... ]

Вы также можете легко получить опции выбора HTML:

var html = enum1.GetSelectOptionsHTML();

Который содержит:

"<option value='0'>item1</option>..."

Я только что опубликовал пакет NPM gen_enum позволяющий быстро создавать структуру данных Enum в Javascript:

var genEnum = require('gen_enum');

var AppMode = genEnum('SIGN_UP, LOG_IN, FORGOT_PASSWORD');
var curMode = AppMode.LOG_IN;
console.log(curMode.isLogIn()); // output true 
console.log(curMode.isSignUp()); // output false 
console.log(curMode.isForgotPassword()); // output false 

Хорошая вещь об этом маленьком инструменте - в современной среде (включая nodejs и браузеры IE 9+) возвращаемый объект Enum является неизменным.

Для получения дополнительной информации, пожалуйста, проверьте https://github.com/greenlaw110/enumjs

Обновления

Устаревший gen_enumпакет и объединяю функцию в пакет constjs , который предоставляет больше возможностей, включая неизменяемые объекты, десериализацию строк JSON, константы строк, генерацию растровых изображений и т. Д. Для получения дополнительной информации обратитесь к constjs

Чтобы обновить , gen_enumчтобы constjsпросто изменить заявление

var genEnum = require('gen_enum');

в

var genEnum = require('constjs').enum;






enums