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

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

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

// later on

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

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

Не используйте 0 в качестве номера перечисления. Если только он не используется для чего-то, что не было установлено. JS рассматривает false || undefined || null || 0 || "" || '' || NaN как одно и то же значение при сравнении с ==.

matsko 17.01.2015 21:10

@matsko - это не просто аргумент против использования ==?

sdm350 25.02.2015 00:40
0 == null возвращает false
mcont 03.04.2015 17:58

Но false == 0 и +null == 0 (и преобразования в числа случаются иногда, когда вы этого не ожидаете), а также null == undefined и +undefined - это NaN (хотя и NaN != NaN).

sanderd17 30.05.2015 18:59

Матрица двойного равенства сбивает с толку больше, чем автоматическое форматирование Microsoft Word.

aaaaaa 23.03.2016 23:32

Этот метод, который вы упомянули в своем вопросе, весьма полезен для NodeJS. Замена my.namespace.ColorEnum = на module.export = в пустом файле дает вам перечисление в собственном файле NodeJS. Блестяще.

Tim Visée 13.04.2016 02:09

Почему перед именем объекта не используется ключевое слово var?

Greener 04.06.2016 00:49

@greener OP показывает, как он добавляет ColorEnum к объекту my.namespace "пространство имен" существующий.

Stijn de Witt 30.06.2016 03:40

Вы даже можете использовать символы в качестве значений ваших ключей, чтобы гарантировать уникальность, см. stackoverflow.com/questions/38993711/…

Simone Poggi 17.08.2016 13:12

@David Citron Не стесняйтесь изменить принятый ответ на stackoverflow.com/a/5040502/3492994, что является лучшим способом создания перечислений в JavaScript.

Stefnotch 10.01.2018 21:43

На самом деле не так важно, как вы определяете константы перечисления. Суть перечислимых типов - гарантировать, что переменная имеет ограниченный набор допустимых значений, а переменные JS не имеют возможности сделать это. Существуют стандартные синтаксисы являются для определения типов перечислений в TypeScript и Flow, хотя это отличается от помещения констант перечисления в какую-то структуру, которую вы можете проверить во время выполнения.

Andy 19.12.2018 04:59

@aaaaaa @matsko матрица двойного равенства не имеет особого значения, если вы привыкли всегда использовать тройное равенство, с единственным полезным исключением, что null == x - это true, если и только если x - это null или undefined. И если вы используете линтер, который помечает отрывочное (/ случайное) использование двойного равенства.

Andy 19.12.2018 05:00

Вы говорите «+ null == 0», как будто это смысл использования triple =, @aaaaaa? Ну ... + null === 0, тоже ...

vbullinger 08.02.2019 23:19
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2 226
13
1 131 053
50
Перейти к ответу Данный вопрос помечен как решенный

Ответы 50

Это не очень хороший ответ, но я бы сказал, что это работает отлично, лично

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

Об этом говорилось в другом ответе, но, поскольку этот ответ является принятым, я опубликую его здесь. Решение OP правильное. Но будет еще лучше, если использовать с Object.freeze(). Это предотвратит изменение значений перечисления другим кодом. Пример: var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2});

Sildoreth 16.01.2014 23:29

@TolgaE, спасибо за эту библиотеку! Это вдохновило меня не только свести его к минимуму, но и добавить пару функций! Я раздвоил ваш и поместил все сюда: github.com/BlueHuskyStudios/Micro-JS-Enum

Ben Leggiero 09.04.2014 09:26

@Supuhstar Отлично! Я рад, что вы могли его использовать ... Не стесняйтесь сделать запрос на перенос, если вы хотите, чтобы он был объединен в этой библиотеке, тогда я могу обновить библиотеку npm

Tolga E 09.04.2014 19:00

Если кому-то интересно, у меня есть типобезопасные перечисления реализовано, похожие на то, как они есть в Java. Это означает, что вы можете выполнять проверки instanceof. Например, ColorEnum.RED instanceof ColorEnum (возвращает true). Вы также можете разрешить экземпляр по имени ColorEnum.fromName("RED") === ColorEnum.RED (возвращает true). Каждый экземпляр также имеет методы .name() и .ordinal(), а само перечисление имеет метод values(), который возвращает массив всех констант.

Vivin Paliath 19.09.2015 01:16

Я не уверен, что согласен с предложением "содержательная строка". Перечисления не следует рассматривать как строки или числа; это абстрактные типы данных. Невозможно «вывести текущее значение» без какого-либо вспомогательного метода. В Java и .NET это метод ToString(). Мы, разработчики JS, уже слишком полагаемся на то, что «просто работает»! Кроме того, можно быстро получить switch по перечислению. Сравнение строк происходит медленнее, чем сравнение чисел, поэтому вы получите немного худшую производительность switch, если будете использовать строки вместо целых чисел.

Rabadash8820 06.07.2017 21:51
Обновление ES6: используйте прокси JavaScript для имитации Enums. Выдавать ошибки времени компиляции при попытке доступа к несуществующим перечислителям - точно так же, как традиционные перечисления. 10 строк кода. stackoverflow.com/a/49309248/2757916
Govind Rai 12.12.2018 02:12

@GovindRai Пожалуйста, ??? ??? ???????: прокси выдают исключение только при доступе к ним (есть ?? "???????-????" ??????), ужасно ???-?????????? и строго ????? ???????? ????. Для решения, которое решает все эти проблемы, см. stackoverflow.com/a/50355530/5601591

Jack Giffin 10.07.2019 21:08

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

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

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

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

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

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

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

What about Artur Czajka's Object.freeze? Wouldn't that work to prevent you from setting monday to thursday? – Fry Quad

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

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

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

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

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

есть ли безопасность типов в javascript?

Scott Evernden 22.08.2009 01:02

Поэтому не сопоставляйте значения со свойствами объекта. Используйте геттер для доступа к перечислимому объекту (хранящемуся как свойство, скажем, «частного» объекта). Наивная реализация выглядела бы так - var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1

kangax 22.08.2009 07:20

@ Скотт Эвернден: точка взята. @kangax: дело в том, что это все еще хак. Перечислений просто не существует в Javascript, точка, конец истории. Даже шаблон, предложенный Тимом Сильвестром, по-прежнему далеко не идеален.

Randolpho 23.08.2009 00:14

Добавление в код литералов не очень удобно, поэтому имеет смысл создавать для него константы. Конечно, в Javascript тоже нет констант. По сути, это просто способ написать чистый код. Это невозможно принудительно, но в Javascript это не так. Вы можете переопределить константы, функции или что угодно еще. НАПРИМЕР: document.getElementById = function () {alert ("Вы облажались. Javascript небезопасен.");};

Stijn de Witt 29.11.2011 15:04

@Randolpho: А как насчет Object.freeze Артура Чайки? Разве это не помешает вам перенести с понедельника на четверг?

Michael 05.01.2012 19:58

На практике я никогда не беспокоился о том, чтобы кто-то присваивал константу перечисления ... это прямо там с беспокойством о том, что кто-то может переопределить undefined. Ошибки, о которых стоит беспокоиться, - это недопустимые значения, присваиваемые переменной, которая должна быть перечислимым типом. Object.freeze не может с этим помочь.

Andy 25.07.2018 03:07

Обновлено

Спасибо за все положительные отзывы, но я не думаю, что мой ответ ниже - лучший способ писать перечисления в 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, поскольку это динамический язык, позже можно даже добавить значения перечисления в набор:

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

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

// 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

Итак, как бы вы могли создать просто РАЗМЕР, если бы у вас было только его имя?

Johanisma 10.11.2011 08:06

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

Stijn de Witt 29.11.2011 14:43

+1 за ссылку на ваш пост с подходом свойств. Элегантно в том, что основные объявления просты, как в OP, с добавлением свойств при желании.

goodeye 29.04.2014 19:35

@Stijin, очень понравилось твое обновленное решение. Размещенный код в комментариях к вашему блогу и в комментариях ниже. В основном, используя функцию, выполните построение свойств из существующего списка хэшей и, возможно, заморозьте его (mkenum_2 в моем списке). Ваше здоровье.

Andrew Philips 05.09.2014 23:10

Существует также библиотека, которая его реализует, а также включает полезные функции для сравнения и обратного поиска: github.com/adrai/enum

Roman M 27.09.2014 19:35

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

Kevin Wiskia 09.08.2016 17:55

@KevinWiskia Да, метод toJSON поможет вам на полпути. Но вам также понадобится reviver для десериализации ... И этот восстановитель должен быть передан на JSON.parse ... Так что вы не можете просто добавить fromJSON или что-то в этом роде и покончить с этим. Вам нужно следить за тем, чтобы эта функция оживления использовалась всякий раз, когда JSON может содержать ваше причудливое перечисление ... Так что это значительно усложняет работу.

Stijn de Witt 11.08.2016 00:31
Ответ принят как подходящий

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

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

или же

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

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

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

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

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

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

Согласно Википедии (en.wikipedia.org/wiki/JavaScript#Versions), он применим к Firefox 4, IE 9, Opera 11.60, и я знаю, что он работает в Chrome.

Artur Czajka 15.03.2012 15:05

Это правильный ответ сейчас, в 2012 году. Проще: var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });. Вам не нужно указывать идентификатор, вы можете просто использовать пустой объект для сравнения перечислений. if (incommingEnum === DaysEnum.monday) //incommingEnum is monday

Gabriel Llamas 07.04.2012 14:29

Для обратной совместимости if (Object.freeze) { Object.freeze(DaysEnum); }

saluce 24.08.2012 19:56

Я хотел бы указать, что выполнение ({ monday: {}, и т. д. Означает, что если вы конвертируете этот объект в JSON с помощью stringify, вы получите [{"day": {}}], который не будет работать.

jcollum 01.02.2013 04:20

@jcollum: Вы можете объяснить? Почему бы stringify не найти имена «понедельник», «вторник» и т. д.? И откуда взялась эта «дневная» струна? Или ваш комментарий относится к квадратным скобкам ([и])?

Stijn de Witt 01.02.2013 23:56

@StijndeWitt Я почти уверен, что stringify превращает объект в "propname": "propvalue" - в этом случае значение prop является пустым объектом (если входящий объект выглядит как {day: DaysEnum.monday}).

jcollum 02.02.2013 02:47

@StijndeWitt Я в конечном итоге использовал эту схему перечисления, просто использовал что-то вроде {monday: "m", tuesday: "t"}, чтобы я мог отправлять значения перечисления по проводу.

jcollum 02.02.2013 02:48

@jcollum: Я бы подумал, что это будет странно. Что делает stringify, когда вы отправляете ему объект с атрибутами, которые не являются строками или числами, а являются массивами или объектами? Он должен был бы закодировать их как «имя»: [] или «имя»: {} верно? Я не уверен, что это действительно проблема, пока не увижу пример :)

Stijn de Witt 04.02.2013 18:52

@StijndeWitt jsfiddle.net/jcollum/eesAN/4 вам будет сложно превратить фокстрот обратно в alpha.a после преобразования в строку

jcollum 04.02.2013 21:24

@jcollum: Хорошо, теперь я получил ваш пример. Итак, вы говорите, что «перечисления», значения которых являются объектами (независимо от того, заморожены они или нет), нельзя сериализовать туда и обратно с помощью stringify, потому что впоследствии они не пройдут проверку личности? Так что, если вам это нужно, вы должны либо использовать числа или другие «объекты-значения», либо реализовать настраиваемую строку ... Но действительно очень хороший момент, я никогда не думал об этом раньше.

Stijn de Witt 19.02.2013 18:13

@saluce не означает ли это, что в IE могут быть ошибки, которых не было бы в остальной части браузера? Кажется опасным использовать это, так как он не будет работать до IE 9, поэтому у вас будет хороший случай для конкретных ошибок IE :(, грустно, глядя на совместимость, FF 4, Chrome 6, IE 9: O

John 28.02.2013 02:10

@John Вы можете определить Object.freeze как проходной без операций, если он не существует. Это не решит проблему, но сделает решение несколько совместимым.

Artur Czajka 04.03.2013 10:00

Если вы действительно хотите использовать номер, вы можете получить его с помощью DaysEnum.monday.toString().

Dan 19.03.2013 23:32

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

Sildoreth 16.01.2014 23:23

@GabrielLlamas Иногда, тем не менее, вам нужен порядковый номер перечисляемой константы.

Ben Leggiero 09.04.2014 09:35

@Supuhstar Мое мнение по этому вопросу сейчас другое. Не используйте freeze (), это совершенно бесполезно и пустая трата времени на «глупые» вещи. Если вы хотите раскрыть перечисление, просто сделайте это: var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}. Сравнение объектов, как в моем предыдущем комментарии, НАМНОГО МЕДЛЕЕ, чем сравнение чисел.

Gabriel Llamas 09.04.2014 11:58

@Supuhstar - Использование порядкового номера выходит за рамки цели структуры перечисления. Перечисления должны строго использоваться для равенства. Установка значений для пустых объектов может быть медленнее, но это предотвращает неправильное использование перечисления - например, с порядковыми числами у разработчика возникнет соблазн использовать перечисление для сортировки. Лучше подойдет другая конструкция.

aaaaaa 23.03.2016 22:28

Проблема, которую я заметил при выполнении: ({понедельник: {}, вторник: {} и т. д., Заключалась в том, что при тестировании вы не получали очень полезных сообщений, когда он терпит неудачу. Например: AssertionError: ожидалось, что {} будет равно {}

Julian Mann 21.08.2016 02:09

@CasBloem const по-прежнему позволит вам изменять значения объекта.

George 12.04.2018 16:21

@GabrielLlamas сравнивает объекты с === довольно быстро. Теоретически это должно быть примерно таким же, как сравнение двух чисел, потому что оно сводится к проверке равенства двух значений указателя (целых чисел).

Andy 25.07.2018 03:15

@Andy Это вдвое сложнее сравнения двух чисел (иногда тройных), потому что браузер также должен проверять тип. Конечно, конечно, браузер может попытаться угадать тип и обычно оказывается прав. Но никто не может знать наверняка, потому что нам еще предстоит избавиться от злого метода eval (var innocentInt = 0, bigObject = {}; eval("innocentInt = bigObject");).

Jack Giffin 26.07.2018 03:29

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

Andy 27.07.2018 04:26

@ Энди Нет. Это не так. Например, function sumItUp(x){x|=0; var sum = 0; while (x > 0) { sum = (sum + x) | 0; x--; } return sum; }. В этой функции нет возможности, чтобы x или сумма не были целыми числами. Таким образом, браузер может безопасно удалить проверку типа из операций, связанных с x и суммой, вместо этого напрямую передавать Javascript прямо в быструю сборку, которая использует статически типизированные целые числа для выполнения суммирования. Очевидно, IE (или любой кусок дерьма, сделанный моим Microsoft в этом отношении) слишком варварский и архаичный для этих оптимизаций, но Chrome и FF делают

Jack Giffin 28.11.2018 03:34
Обновление ES6: используйте прокси-серверы JavaScript для создания класса Enum. Выдает ошибки времени компиляции при попытке доступа к несуществующим перечислителям или при добавлении / обновлении перечислителей - точно так же, как традиционные перечисления. 15 строк кода.
Govind Rai 12.12.2018 02:10

@GovindRai Пожалуйста, ??? ??? ???????: прокси выдают исключение только при доступе к ним (есть ?? "???????-????" ??????), ужасно ???-?????????? и строго ????? ???????? ????. Для решения, которое решает все эти проблемы, см. stackoverflow.com/a/50355530/5601591

Jack Giffin 10.07.2019 21:06

Что именно делает замораживание?

Aaron Franke 06.12.2019 07:23

@AaronFranke См. Связанный источник: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

Artur Czajka 09.12.2019 03:28

@AaronFranke ?????? ?? ???????? ??? ??????????? при таком использовании! Поиск перечислений для объектов выполняется медленно даже с JIT. Для максимальной производительности вам нужен var b=2 в минифицированном выводе, а не var b=a.tuesday (где a - это DaysEnum, а b - это день). Однако ввод чисел в исходный код затрудняет / делает невозможным чтение, поддержку и передачу. Компилятор закрытия с ADVANCED_OPTIMIZATIONS может вставлять переменные в виде чисел. Однако он не может быть встроен при использовании Object.freeze. Подробнее см. stackoverflow.com/a/50355530/5601591.

Jack Giffin 22.02.2020 21:22

@JackGiffin, часть вашего ответа, связанная с производительностью, не так ясна. Есть давно обновленный ответ на другой вопрос, который показывает, что разницы в производительности практически нет: stackoverflow.com/a/23198265/572370. Я бы сказал, все зависит от конкретного варианта использования и каждый должен проверить свою производительность.

Artur Czajka 24.02.2020 16:30

@ArturCzajka Я возражаю не в том, что замороженные объекты плохо работают, а в том, что Object.freeze мешает производительности минифицированного производственного кода. Посетите эта страница. С заморозкой это alert(a.a). Без него это alert(1).

Jack Giffin 25.02.2020 13:40

Вот чего мы все хотим:

function Enum(constantsList) {
    for (var i in constantsList) {
        this[constantsList[i]] = i;
    }
}

Теперь вы можете создавать свои перечисления:

var YesNo = new Enum(['NO', 'YES']);
var Color = new Enum(['RED', 'GREEN', 'BLUE']);

Таким образом, к константам можно обращаться обычным способом (YesNo.YES, Color.GREEN), и они получают последовательное значение int (NO = 0, YES = 1; RED = 0, GREEN = 1, BLUE = 2).

Вы также можете добавить методы, используя Enum.prototype:

Enum.prototype.values = function() {
    return this.allValues;
    /* for the above to work, you'd need to do
            this.allValues = constantsList at the constructor */
};


Изменить - небольшое улучшение - теперь с varargs: (к сожалению, он не работает должным образом в IE: S ... тогда следует придерживаться предыдущей версии)

function Enum() {
    for (var i in arguments) {
        this[arguments[i]] = i;
    }
}

var YesNo = new Enum('NO', 'YES');
var Color = new Enum('RED', 'GREEN', 'BLUE');

Мне нравится простота этого ответа!

Marquizzo 20.06.2019 01:00

@Marquizzo (и OP) Я создал улучшенную версию на основе этого ответа: stackoverflow.com/a/60309416/1599699

Andrew 20.02.2020 00:26

@Andrew Я создал отдельный и гораздо более продуманный, тщательно продуманный и тщательно проверенный ответ, который я много раз использовал в продакшене: stackoverflow.com/a/50355530/5601591

Jack Giffin 23.07.2020 02:43

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

// enum instance members, optional
var Color = Backbone.Model.extend({
    print : function() {
        console.info("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()

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

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.

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

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

Я модифицировал решение Андре Фи:

  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

Это старый, который я знаю, но с тех пор он был реализован через интерфейс 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», независимо от порядка объявления.

Плюс MyEnum ["Bar"] работает, что пока возвращает 1 ... <3 TypeScript ...

David Karlaš 05.01.2014 13:41

и, конечно, если вы действительно ИСПОЛЬЗУЕТЕ Typescript: enum MyEnum { Foo, Bar, Foobar }

parliament 29.07.2015 02:42

Я играл с этим, так как мне нравятся мои перечисления. знак равно

Используя Object.defineProperty, я думаю, что нашел несколько жизнеспособное решение.

Вот jsfiddle: http://jsfiddle.net/ZV4A6/

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

Object.defineProperty(Object.prototype,'Enum', {
    value: function() {
        for(i in arguments) {
            Object.defineProperty(this,arguments[i], {
                value:parseInt(i),
                writable:false,
                enumerable:true,
                configurable:true
            });
        }
        return this;
    },
    writable:false,
    enumerable:false,
    configurable:false
}); 

Благодаря атрибуту writable:false этот должен делает его типобезопасным.

Таким образом, вы должны иметь возможность создать собственный объект, а затем вызвать для него Enum(). Назначенные значения начинаются с 0 и увеличиваются для каждого элемента.

var EnumColors = {};
EnumColors.Enum('RED','BLUE','GREEN','YELLOW');
EnumColors.RED;    // == 0
EnumColors.BLUE;   // == 1
EnumColors.GREEN;  // == 2
EnumColors.YELLOW; // == 3

Если вы добавите return this; в конце Enum, вы можете сделать: var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW');

HBP 21.08.2013 21:15

Я не учел этого, потому что это не мой нормальный метод ведения дел. Но вы абсолютно правы! Я отредактирую это в.

Duncan 21.08.2013 21:20

Мне это очень нравится, хотя я не большой поклонник мусора в пространстве объектов (с глобальной функцией ENUM). Преобразовал это в функцию mkenum и добавил необязательные числовые присвоения => var mixedUp = mkenum ('ЧЕРНЫЙ', {КРАСНЫЙ: 0x0F00, СИНИЙ: 0X0F, ЗЕЛЕНЫЙ: 0x0F0, БЕЛЫЙ: 0x0FFF, ONE: 1}, ДВА, ТРИ, ЧЕТЫРЕ) ; // Добавляем мой код в качестве ответа ниже. Спасибо.

Andrew Philips 05.09.2014 22:44

Если честно, я этим больше даже не пользуюсь. Я использовал компилятор Google Closure Compiler, и он не работает слишком хорошо (или просто усложняет ситуацию), если вы используете расширенную настройку. Итак, я только что вернулся к стандартной объектной нотации.

Duncan 08.09.2014 21:32
false используется по умолчанию для writable, enumerable и configurable. Не нужно пережевывать настройки по умолчанию.
ceving 02.11.2015 19:03

Я сделал это некоторое время назад, используя смесь __defineGetter__ и __defineSetter__ или defineProperty, в зависимости от версии JS.

Вот созданная мной функция генерации перечисления: https://gist.github.com/gfarrell/6716853

Вы бы использовали это так:

var Colours = Enum('RED', 'GREEN', 'BLUE');

И это создаст неизменную строку: int dictionary (enum).

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

GTF 26.09.2013 21:03

Быстрый и простой способ:

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

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

Функция не нужна и дает тот же результат, что и OP.

Sildoreth 16.01.2014 23:19

Вы также можете попробовать определить новую функцию и, следовательно, новое пространство имен, и добавить к нему переменные, как это.

function Color () {};  
Color.RED = 1;
Color.YELLOW = 2;

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

Я считаю, что очень простой способ определить перечисления.

Надеюсь, это поможет!

Привет.

Виктор.

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

Sildoreth 17.01.2014 00:15

я был скорее прагматичным, чем теоретическим. Я как раз отвечал на вопрос, и для ТОЛЬКО определения констант нет особой пользы от знания большего, чем это. Имя нужно помнить всегда, всегда.

Victor 04.02.2014 03:07

Добавление настраиваемых свойств к функциям - плохая практика и не очень эффективна.

Jack Giffin 15.05.2018 20:02

@Sildoreth (то же самое, что и Джек) в современном машинописном тексте делает такие вещи «за кулисами для нас» (я имею в виду, что переведенный код делает такие вещи, как добавление свойства в функцию для реализации статических членов класса (для ES5)), поэтому я не понимаю, почему это не рекомендуется?

Victor 02.12.2019 00:38

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

Victor 02.12.2019 00:41

На момент написания Октябрь 2014 г. - так что вот современное решение. Я пишу решение как Node Module и включил тест с использованием Mocha и Chai, а также underscoreJS. Вы можете легко проигнорировать их и просто взять код Enum, если хотите.

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

Файл: enums.js

_ = require('underscore');

var _Enum = function () {

   var keys = _.map(arguments, function (value) {
      return value;
   });
   var self = {
      keys: keys
   };
   for (var i = 0; i < arguments.length; i++) {
      self[keys[i]] = i;
   }
   return self;
};

var fileFormatEnum = Object.freeze(_Enum('CSV', 'TSV'));
var encodingEnum = Object.freeze(_Enum('UTF8', 'SHIFT_JIS'));

exports.fileFormatEnum = fileFormatEnum;
exports.encodingEnum = encodingEnum;

И тест, чтобы проиллюстрировать, что это дает вам:

файл: enumsSpec.js

var chai = require("chai"),
    assert = chai.assert,
    expect = chai.expect,
    should = chai.should(),
    enums = require('./enums'),
    _ = require('underscore');


describe('enums', function () {

    describe('fileFormatEnum', function () {
        it('should return expected fileFormat enum declarations', function () {
            var fileFormatEnum = enums.fileFormatEnum;
            should.exist(fileFormatEnum);
            assert('{"keys":["CSV","TSV"],"CSV":0,"TSV":1}' === JSON.stringify(fileFormatEnum), 'Unexpected format');
            assert('["CSV","TSV"]' === JSON.stringify(fileFormatEnum.keys), 'Unexpected keys format');
        });
    });

    describe('encodingEnum', function () {
        it('should return expected encoding enum declarations', function () {
            var encodingEnum = enums.encodingEnum;
            should.exist(encodingEnum);
            assert('{"keys":["UTF8","SHIFT_JIS"],"UTF8":0,"SHIFT_JIS":1}' === JSON.stringify(encodingEnum), 'Unexpected format');
            assert('["UTF8","SHIFT_JIS"]' === JSON.stringify(encodingEnum.keys), 'Unexpected keys format');
        });
    });

});

Как видите, у вас есть фабрика Enum, вы можете получить все ключи, просто вызвав enum.keys, и вы можете сопоставить сами ключи с целочисленными константами. И вы можете повторно использовать фабрику с другими значениями и экспортировать эти сгенерированные перечисления, используя модульный подход Node.

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

Не могли бы вы опубликовать ответ просто с фразой «вот как это сделать, если вы обычный пользователь, которому нужны только перечисления, а не фабрики, символы подчеркивания или что-нибудь необычное»?

GreenAsJade 02.01.2015 06:08

Несмотря на то, что это довольно круто с точки зрения разработчиков, оно не очень чистое и читабельное. Решение Enum из OP проще и читабельнее во всех отношениях, поэтому его лучше использовать. Тем не менее, довольно круто, что вы это придумали.

David 17.01.2015 22:55

ваши ответы слишком сложны

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

@JackGiffin Я согласен с тем, что ваш ответ более эффективен и что мой может занять больше памяти, хотя вы не должны предполагать, что всем нужно перечисление так, как это реализовано в C++. Пожалуйста, уважайте другие ответы и разработчиков, которые могут предпочесть этот ответ вашему.

Xeltor 14.12.2018 18:09

Мне действительно нравится то, что сделал @Duncan выше, но мне не нравится убирать глобальное функциональное пространство Object с помощью Enum, поэтому я написал следующее:

function mkenum_1()
{
  var o = new Object();
  var c = -1;
  var f = function(e, v) { Object.defineProperty(o, e, { value:v, writable:false, enumerable:true, configurable:true })};

  for (i in arguments) {
    var e = arguments[i];
    if ((!!e) & (e.constructor == Object))
      for (j in e)
        f(j, (c=e[j]));
    else
      f(e, ++c);
    }

  return Object.freeze ? Object.freeze(o) : o;
}

var Sizes = mkenum_1('SMALL','MEDIUM',{LARGE: 100},'XLARGE');

console.info("MED := " + Sizes.MEDIUM);
console.info("LRG := " + Sizes.LARGE);

// Output is:
// MED := 1
// LRG := 100

У @Stijin также есть изящное решение (ссылаясь на его блог), которое включает свойства этих объектов. Для этого я тоже написал код, который буду включать в следующий раз.

function mkenum_2(seed)
{
    var p = {};

    console.info("Seed := " + seed);

    for (k in seed) {
        var v = seed[k];

        if (v instanceof Array)
            p[(seed[k]=v[0])] = { value: v[0], name: v[1], code: v[2] };
        else
            p[v] = { value: v, name: k.toLowerCase(), code: k.substring(0,1) };
    }
    seed.properties = p;

    return Object.freeze ? Object.freeze(seed) : seed;
}

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

var SizeEnum2 = mkenum_2({ SMALL: 1, MEDIUM: 2, LARGE: 3});
var SizeEnum3 = mkenum_2({ SMALL: [1, "small", "S"], MEDIUM: [2, "medium", "M"], LARGE: [3, "large", "L"] });

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

var ColorEnum = {
    red: {},
    green: {},
    blue: {}
}

Вам не нужно следить за тем, чтобы таким образом вы не присваивали повторяющиеся числа разным значениям перечисления. Создается экземпляр нового объекта, которому присваиваются все значения перечисления.

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

Domino 09.12.2015 00:16

Хм, только убедитесь, что этот код не вызывается дважды ...

Andrew 19.02.2020 23:22

Вы можете использовать Object.prototype.hasOwnProperty ()

var findInEnum,
    colorEnum = {
    red : 0,
    green : 1,
    blue : 2
};

// later on

findInEnum = function (enumKey) {
  if (colorEnum.hasOwnProperty(enumKey)) {
    return enumKey+' Value: ' + colorEnum[enumKey]
  }
}

alert(findInEnum("blue"))

Вот несколько способов реализовать Перечисления TypeScript.

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

function _enum(list) {       
  for (var key in list) {
    list[list[key] = list[key]] = key;
  }
  return Object.freeze(list);
}

var Color = _enum({
  Red: 0,
  Green: 5,
  Blue: 2
});

// Color → {0: "Red", 2: "Blue", 5: "Green", "Red": 0, "Green": 5, "Blue": 2}
// Color.Red → 0
// Color.Green → 5
// Color.Blue → 2
// Color[5] → Green
// Color.Blue > Color.Green → false


А вот примесь lodash для создания перечисления с использованием строки. Хотя эта версия немного сложнее, она автоматически выполняет нумерацию. Все методы lodash, используемые в этом примере, имеют обычный эквивалент JavaScript, поэтому вы можете легко отключить их, если хотите.

function enum() {
    var key, val = -1, list = {};
    _.reduce(_.toArray(arguments), function(result, kvp) {    
        kvp = kvp.split(" = ");
        key = _.trim(kvp[0]);
        val = _.parseInt(kvp[1]) || ++val;            
        result[result[val] = key] = val;
        return result;
    }, list);
    return Object.freeze(list);
}    

// Add enum to lodash 
_.mixin({ "enum": enum });

var Color = _.enum(
    "Red",
    "Green",
    "Blue = 5",
    "Yellow",
    "Purple = 20",
    "Gray"
);

// Color.Red → 0
// Color.Green → 1
// Color.Blue → 5
// Color.Yellow → 6
// Color.Purple → 20
// Color.Gray → 21
// Color[5] → Blue

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

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

Итак, для этого вам нужны объекты с одним и тем же экземпляром, чтобы вы могли проверить, является ли это перечислимым типом if (something instanceof enum). Кроме того, если вы получаете объект перечисления, с которым хотите работать, независимо от типа перечисления, он всегда должен отвечать одинаково.

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

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


Это то, что я использую для Enums. Поскольку перечисления неизменяемы (или должны быть, по крайней мере, хе-хе), я замораживаю объекты, чтобы ими нельзя было легко манипулировать.

Перечисления могут использоваться EnumField.STRING, и у них есть свои собственные методы, которые будут работать с их типами. Чтобы проверить, было ли что-то передано объекту, вы можете использовать if (somevar instanceof EnumFieldSegment)

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

Я также понимаю, что мог бы переопределить прототип с помощью {}, но мой разум лучше работает с этим форматом ;-) стреляйте в меня.

/**
 * simple parameter object instantiator
 * @param name
 * @param value
 * @returns
 */
function p(name,value) {
    this.name = name;
    this.value = value;
    return Object.freeze(this);
}
/**
 * EnumFieldSegmentBase
 */
function EnumFieldSegmentBase() {
    this.fieldType = "STRING";
}
function dummyregex() {
}
dummyregex.prototype.test = function(str) {
    if (this.fieldType === "STRING") {
        maxlength = arguments[1];
        return str.length <= maxlength;
    }
    return true;
};

dummyregexposer = new dummyregex();
EnumFieldSegmentBase.prototype.getInputRegex = function() { 
    switch(this.fieldType) {
        case "STRING" :     return dummyregexposer;  
        case "INT":         return /^(\d+)?$/;
        case "DECIMAL2":    return /^\d+(\.\d{1,2}|\d+|\.)?$/;
        case "DECIMAL8":    return /^\d+(\.\d{1,8}|\d+|\.)?$/;
        // boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its  false, otherwise lets see what Boolean produces
        case "BOOLEAN":     return dummyregexposer;
    }
};
EnumFieldSegmentBase.prototype.convertToType = function($input) {
    var val = $input;
    switch(this.fieldType) {
        case "STRING" :         val = $input;break;
        case "INT":         val= = ""? val=0 :val = parseInt($input);break;
        case "DECIMAL2":    if ($input === "" || $input === null) {$input = "0"}if ($input.substr(-1) === "."){$input = $input+0};val = new Decimal2($input).toDP(2);break;
        case "DECIMAL8":    if ($input === "" || $input === null) {$input = "0"}if ($input.substr(-1) === "."){$input = $input+0};val = new Decimal8($input).toDP(8);break;
        // boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its  false, otherwise lets see what Boolean produces
        case "BOOLEAN":     val = (typeof $input == 'boolean' ? $input : (typeof $input === 'string' ? (($input === "false" || $input === "" || $input === "0") ? false : true) : new Boolean($input).valueOf()))  ;break;
    }
    return val;
};
EnumFieldSegmentBase.prototype.convertToString = function($input) {
    var val = $input;
    switch(this.fieldType) {
        case "STRING":      val = $input;break;
        case "INT":         val = $input+"";break;
        case "DECIMAL2":    val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+2 : $input.toString().indexOf('.')+2)) ;break;
        case "DECIMAL8":    val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+8 : $input.toString().indexOf('.')+8)) ;break;
        case "BOOLEAN":     val = $input ? "true" : "false"  ;break;
    }
    return val;
};
EnumFieldSegmentBase.prototype.compareValue = function($val1,$val2) {
    var val = false;
    switch(this.fieldType) {
        case "STRING":      val = ($val1===$val2);break;
        case "INT":         val = ($val1===$val2);break;
        case "DECIMAL2":    val = ($val1.comparedTo($val2)===0);break;
        case "DECIMAL8":    val = ($val1.comparedTo($val2)===0);break;
        case "BOOLEAN":     val = ($val1===$val2);break;
    }
    return val;
};

/**
 * EnumFieldSegment is an individual segment in the 
 * EnumField
 * @param $array An array consisting of object p
 */
function EnumFieldSegment() {
    for(c=0;c<arguments.length;c++) {
        if (arguments[c] instanceof p) {
            this[arguments[c].name] = arguments[c].value;
        }
    }
    return Object.freeze(this); 
}
EnumFieldSegment.prototype = new EnumFieldSegmentBase();
EnumFieldSegment.prototype.constructor = EnumFieldSegment;


/**
 * Simple enum to show what type of variable a Field type is.
 * @param STRING
 * @param INT
 * @param DECIMAL2
 * @param DECIMAL8
 * @param BOOLEAN
 * 
 */
EnumField = Object.freeze({STRING:      new EnumFieldSegment(new p("fieldType","STRING")), 
                            INT:        new EnumFieldSegment(new p("fieldType","INT")), 
                            DECIMAL2:   new EnumFieldSegment(new p("fieldType","DECIMAL2")), 
                            DECIMAL8:   new EnumFieldSegment(new p("fieldType","DECIMAL8")), 
                            BOOLEAN:    new EnumFieldSegment(new p("fieldType","BOOLEAN"))});

Я только что опубликовал пакет 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.info(curMode.isLogIn()); // output true 
console.info(curMode.isSignUp()); // output false 
console.info(curMode.isForgotPassword()); // output false 

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

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

Обновления

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

Для обновления с gen_enum до constjs просто измените инструкцию

var genEnum = require('gen_enum');

к

var genEnum = require('constjs').enum;
var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });

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

if (incommingEnum === DaysEnum.monday) //incommingEnum is monday

Обновлено: Если вы собираетесь сериализовать объект (например, в JSON), вы снова получите я бы.

{red:{green:{blue:{}}}}.unwindme()!
Little Alien 03.11.2016 00:30

Пожалуйста, попробуйте это: if (JSON.parse(JSON.stringify(DaysEnum.monday)) != DaysEnum.monday) console.error('Oops!'). Прочтите мое сообщение в блоге Перечисления в Javascript, посвященное именно этой проблеме, чтобы узнать, почему это происходит.

Stijn de Witt 01.02.2017 12:51

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

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

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

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

Демо Plunker

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

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

Теоретически это правильный ответ. На практике поддержки браузеров 2015 года далеко не достаточно. Еще не готово к производству.

vbraun 16.06.2015 11:14

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

rvighne 13.07.2016 01:49

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

Andy 25.07.2018 03:09

Это только у меня, или Object.freeze только для людей, которые не приняли тот факт, что «обезьяны на свой страх и риск» - это общественный договор JS?

Andy 19.12.2018 05:10

@Andy да сериализация раздражает. Я закончил тем, что сделал явный toJSON для содержащего класса, чтобы использовать этот подход: stackoverflow.com/questions/58499828/…

Ciro Santilli新疆棉花TRUMP BAN BAD 22.10.2019 11:33

IE8 не поддерживает метод freeze (). Источник: http://kangax.github.io/compat-table/es5/, нажмите "Показать устаревшие браузеры?" вверху и проверьте пересечение IE8 и закрепления строки столбца.

В моем текущем игровом проекте я использовал ниже, поскольку немногие клиенты все еще используют IE8:

var CONST_WILD_TYPES = {
    REGULAR: 'REGULAR',
    EXPANDING: 'EXPANDING',
    STICKY: 'STICKY',
    SHIFTING: 'SHIFTING'
};

Мы также могли:

var CONST_WILD_TYPES = {
    REGULAR: 'RE',
    EXPANDING: 'EX',
    STICKY: 'ST',
    SHIFTING: 'SH'
};

или даже это:

var CONST_WILD_TYPES = {
    REGULAR: '1',
    EXPANDING: '2',
    STICKY: '3',
    SHIFTING: '4'
};

Последний вариант кажется наиболее эффективным для строки, он снижает общую пропускную способность, если у вас есть сервер и клиент, обменивающиеся этими данными. Конечно, теперь ваша обязанность - убедиться, что в данных нет конфликтов (RE, EX и т. д. Должны быть уникальными, а также 1, 2 и т. д. Должны быть уникальными). Обратите внимание, что вам нужно хранить их вечно для обратной совместимости.

Назначение:

var wildType = CONST_WILD_TYPES.REGULAR;

Сравнение:

if (wildType === CONST_WILD_TYPES.REGULAR) {
    // do something here
}

Создайте литерал объекта:

const Modes = {
  DRAGGING: 'drag',
  SCALING:  'scale',
  CLICKED:  'click'
};
const не делает свойства объекта неизменяемыми, это только означает, что переменную Modes нельзя переназначить на что-то другое. Чтобы сделать его более полным, используйте Object.freeze() вместе с const.
rvighne 13.07.2016 01:47

Пожалуйста, не используйте Object.freeze. Это не позволяет компилятору Closure встраивать объект.

Jack Giffin 07.10.2018 16:21

Думаю, им легко пользоваться. https://stackoverflow.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"]));

Я придумал подход это, который смоделирован на основе перечислений в Java. Они безопасны по типу, поэтому вы также можете выполнять проверки instanceof.

Вы можете определить перечисления следующим образом:

var Days = Enum.define("Days", ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]);

Days теперь относится к перечислению Days:

Days.Monday instanceof Days; // true

Days.Friday.name(); // "Friday"
Days.Friday.ordinal(); // 4

Days.Sunday === Days.Sunday; // true
Days.Sunday === Days.Friday; // false

Days.Sunday.toString(); // "Sunday"

Days.toString() // "Days { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } "

Days.values().map(function(e) { return e.name(); }); //["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
Days.values()[4].name(); //"Friday"

Days.fromName("Thursday") === Days.Thursday // true
Days.fromName("Wednesday").name() // "Wednesday"
Days.Friday.fromName("Saturday").name() // "Saturday"

Реализация:

var Enum = (function () {
    /**
     * Function to define an enum
     * @param typeName - The name of the enum.
     * @param constants - The constants on the enum. Can be an array of strings, or an object where each key is an enum
     * constant, and the values are objects that describe attributes that can be attached to the associated constant.
     */
    function define(typeName, constants) {

        /** Check Arguments **/
        if (typeof typeName === "undefined") {
            throw new TypeError("A name is required.");
        }

        if (!(constants instanceof Array) && (Object.getPrototypeOf(constants) !== Object.prototype)) {

            throw new TypeError("The constants parameter must either be an array or an object.");

        } else if ((constants instanceof Array) && constants.length === 0) {

            throw new TypeError("Need to provide at least one constant.");

        } else if ((constants instanceof Array) && !constants.reduce(function (isString, element) {
                return isString && (typeof element === "string");
            }, true)) {

            throw new TypeError("One or more elements in the constant array is not a string.");

        } else if (Object.getPrototypeOf(constants) === Object.prototype && !Object.keys(constants).reduce(function (isObject, constant) {
                return Object.getPrototypeOf(constants[constant]) === Object.prototype;
            }, true)) {

            throw new TypeError("One or more constants do not have an associated object-value.");

        }

        var isArray = (constants instanceof Array);
        var isObject = !isArray;

        /** Private sentinel-object used to guard enum constructor so that no one else can create enum instances **/
        function __() { };

        /** Dynamically define a function with the same name as the enum we want to define. **/
        var __enum = new Function(["__"],
            "return function " + typeName + "(sentinel, name, ordinal) {" +
                "if (!(sentinel instanceof __)) {" +
                    "throw new TypeError(\"Cannot instantiate an instance of " + typeName + ".\");" +
                "}" +

                "this.__name = name;" +
                "this.__ordinal = ordinal;" +
            "}"
        )(__);

        /** Private objects used to maintain enum instances for values(), and to look up enum instances for fromName() **/
        var __values = [];
        var __dict = {};

        /** Attach values() and fromName() methods to the class itself (kind of like static methods). **/
        Object.defineProperty(__enum, "values", {
            value: function () {
                return __values;
            }
        });

        Object.defineProperty(__enum, "fromName", {
            value: function (name) {
                var __constant = __dict[name]
                if (__constant) {
                    return __constant;
                } else {
                    throw new TypeError(typeName + " does not have a constant with name " + name + ".");
                }
            }
        });

        /**
         * The following methods are available to all instances of the enum. values() and fromName() need to be
         * available to each constant, and so we will attach them on the prototype. But really, they're just
         * aliases to their counterparts on the prototype.
         */
        Object.defineProperty(__enum.prototype, "values", {
            value: __enum.values
        });

        Object.defineProperty(__enum.prototype, "fromName", {
            value: __enum.fromName
        });

        Object.defineProperty(__enum.prototype, "name", {
            value: function () {
                return this.__name;
            }
        });

        Object.defineProperty(__enum.prototype, "ordinal", {
            value: function () {
                return this.__ordinal;
            }
        });

        Object.defineProperty(__enum.prototype, "valueOf", {
            value: function () {
                return this.__name;
            }
        });

        Object.defineProperty(__enum.prototype, "toString", {
            value: function () {
                return this.__name;
            }
        });

        /**
         * If constants was an array, we can the element values directly. Otherwise, we will have to use the keys
         * from the constants object.
         */
        var _constants = constants;
        if (isObject) {
            _constants = Object.keys(constants);
        }

        /** Iterate over all constants, create an instance of our enum for each one, and attach it to the enum type **/
        _constants.forEach(function (name, ordinal) {
            // Create an instance of the enum
            var __constant = new __enum(new __(), name, ordinal);

            // If constants was an object, we want to attach the provided attributes to the instance.
            if (isObject) {
                Object.keys(constants[name]).forEach(function (attr) {
                    Object.defineProperty(__constant, attr, {
                        value: constants[name][attr]
                    });
                });
            }

            // Freeze the instance so that it cannot be modified.
            Object.freeze(__constant);

            // Attach the instance using the provided name to the enum type itself.
            Object.defineProperty(__enum, name, {
                value: __constant
            });

            // Update our private objects
            __values.push(__constant);
            __dict[name] = __constant;
        });

        /** Define a friendly toString method for the enum **/
        var string = typeName + " { " + __enum.values().map(function (c) {
                return c.name();
            }).join(", ") + " } ";

        Object.defineProperty(__enum, "toString", {
            value: function () {
                return string;
            }
        });

        /** Freeze our private objects **/
        Object.freeze(__values);
        Object.freeze(__dict);

        /** Freeze the prototype on the enum and the enum itself **/
        Object.freeze(__enum.prototype);
        Object.freeze(__enum);

        /** Return the enum **/
        return __enum;
    }

    return {
        define: define
    }

})();

Выглядит неплохо, может стоит проверить наличие метода freeze на обратную совместимость? Например, if (Object.freeze) { Object.freeze(values); }

FBB 23.09.2015 14:25

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

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.info(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 в основном заводской.

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

Это не совсем перечисление. Но я вижу, что это очень полезно.

user1228 14.07.2016 20:23

@Will, как бы вы их назвали ^^? AugmentedEnum?

Jules Sam. Randolph 14.07.2016 21:07

Я не уверен ... Это своего рода смесь перечислений + метаданных (в вашем примере). У перечисления просто есть имя и значение структуры, в которое оно может быть приведено или из которого. Вы можете разместить неограниченное количество свойств для каждого, используя это. Я видел этот шаблон раньше, и команда, написавшая его, назвала константы результата ... Включила отдельные типы констант, такие как DataType, у которых не только было имя, но и значение идентификатора, допустимые операторы (еще одна группа констант! ), допустимые преобразования и т. д. Действительно полезно, но гораздо больше, чем простое перечисление.

user1228 14.07.2016 21:13

Очень плохой исполнитель. Делает весь код медленнее. Увеличивает минифицированный код в несколько раз. Очень плохой.

Jack Giffin 24.06.2018 02:27

Я создал класс 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);

Вы можете инициализировать это так:

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>..."

Вы можете попробовать это:

   var Enum = Object.freeze({
            Role: Object.freeze({ Administrator: 1, Manager: 2, Supervisor: 3 }),
            Color:Object.freeze({RED : 0, GREEN : 1, BLUE : 2 })
            });

    alert(Enum.Role.Supervisor);
    alert(Enum.Color.GREEN);
    var currentColor=0;
    if (currentColor == Enum.Color.RED) {
       alert('Its Red');
    }

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

    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

Самое простое решение:

Создавать

var Status = Object.freeze({
    "Connecting":0,
    "Ready":1,
    "Loading":2,
    "Processing": 3
});

Получите ценность

console.info(Status.Ready) // 1

Получить ключ

console.info(Object.keys(Status)[Status.Ready]) // Ready

Несмотря на то, что только статические методы (а не статические свойства) поддерживаются в ES2015 (см. Также здесь, §15.2.2.2), любопытно, что вы можете использовать это ниже с Babel с предустановкой 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);

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

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

Обновить

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

  1. больше нельзя создавать объекты типа CellState
  2. вам гарантируется, что никаким двум экземплярам перечисления не назначен один и тот же код
  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;
    

Хороший пример :-)

Ashraf.Shk786 18.05.2018 01:44

Решение Alien - максимально упростить задачу:

  1. использовать ключевое слово enum (зарезервировано в javascript)
  2. Если ключевое слово enum просто зарезервировано, но не реализовано в вашем javascript, определите следующее

    const enumerate = spec => spec.split(/\s*,\s*/)
      .reduce((e, n) => Object.assign(e,{[n]:n}), {}) 
    

Теперь вы можете легко использовать это

const kwords = enumerate("begin,end, procedure,if")
console.info(kwords, kwords.if, kwords.if == "if", kwords.undef)

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

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

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

потом

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

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

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

Не могли бы вы объяснить, почему наличие родительского класса является преимуществом? Я чувствую, что что-то упускаю!

Jon G 01.02.2017 12:57

Не делай этого. new ColorEnum() не имеет абсолютно никакого смысла.

Bergi 09.06.2017 04:43

расширение перечисления звучит безумно, правда

Codii 06.07.2017 16:30

если язык не поддерживает его изначально, имеет смысл сохранить это соглашение и использовать его так! я согласен!

xpto 06.10.2017 10:31

Я думаю (?), К чему стремится OP: Преимущество чистой статики в том, что она доступна повсюду как синглтон, и вы не необходимость для создания экземпляра класса - OP не предлагает, чтобы вы это сделали! Я думаю, он говорит о том, что суперкласс Enum имеет стандартные методы перечислителя статический, такие как getValues(), getNames(), iterate() и т. д. В этом случае вам не нужно заново реализовывать их для каждого нового типа enum.

Engineer 11.02.2020 19:53

Вы можете попробовать использовать https://bitbucket.org/snippets/frostbane/aAjxM.

my.namespace.ColorEnum = new Enum(
    "RED = 0",
    "GREEN",
    "BLUE"
)

Он должен работать до IE8.

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

var objInvert = function (obj) {
    var invert = {}
    for (var i in obj) {
      if (i.match(/^\d+$/)) i = parseInt(i,10)
      invert[obj[i]] = i
    }
    return invert
}
 
var musicStyles = Object.freeze(objInvert(['ROCK', 'SURF', 'METAL',
'BOSSA-NOVA','POP','INDIE']))

console.info(musicStyles)

es7 way, (итератор, замораживание), использование:

const ThreeWiseMen = new Enum('Melchior', 'Caspar', 'Balthazar')

for (let name of ThreeWiseMen)
    console.info(name)


// with a given key
let key = ThreeWiseMen.Melchior

console.info(key in ThreeWiseMen) // true (string conversion, also true: 'Melchior' in ThreeWiseMen)

for (let entry from key.enum)
     console.info(entry)


// prevent alteration (throws TypeError in strict mode)
ThreeWiseMen.Me = 'Me too!'
ThreeWiseMen.Melchior.name = 'Foo'

код:

class EnumKey {

    constructor(props) { Object.freeze(Object.assign(this, props)) }

    toString() { return this.name }

}

export class Enum {

    constructor(...keys) {

        for (let [index, key] of keys.entries()) {

            Object.defineProperty(this, key, {

                value: new EnumKey({ name:key, index, enum:this }),
                enumerable: true,

            })

        }

        Object.freeze(this)

    }

    *[Symbol.iterator]() {

        for (let key of Object.keys(this))
            yield this[key]

    }

    toString() { return [...this].join(', ') }

}

Используйте Javascript Прокси

TL; DR: Добавьте этот класс в свои служебные методы и используйте его во всем коде, он имитирует поведение 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, которую вы получаете от традиционных языков, заключается в том, что они взрываются (выдают ошибку времени компиляции), если вы пытаетесь получить доступ к перечислителю, который не существует.

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

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

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

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

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

Подобно прокси веб-сервера, прокси-серверы 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.info(httpMethods.DELETE)
// logs "DELETE"

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

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

В сторону: Что, черт возьми, такое прокси?

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

Как сделать что-то вроде myEnum.valueOf ("someStringValue")? Ожидается: в случае, если входная строка имеет значение элемента перечислителя, должен вернуть этот элемент. Если ни один элемент не имеет этого строкового значения, генерировать исключение.

sscarduzio 31.01.2019 20:51

@sscarduzio вы можете переопределить метод valueOf по умолчанию, указав его как метод экземпляра в классе Enum. Однако почему вы хотите получить к нему доступ именно таким образом, а не просто получить к нему доступ через точечную нотацию?

Govind Rai 31.01.2019 21:00

Мое перечисление - const logLevelEnum = new Enum ({INFO: "info", DEBUG: "debug"}), и я анализирую из ввода произвольную строку "info" или "debug". Поэтому мне нужно что-то вроде currentLogLevel = logLevelEnum.parseOrThrow (settings.get ("log_level"))

sscarduzio 01.02.2019 16:33

Почему ты просто не мог сделать logLevelEnum[settings.get("log_level")]? добавление parseOrThrow будет повторением того, что ловушки прокси уже делают для вас.

Govind Rai 02.02.2019 01:26

Вот как 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

Этот ответ представляет собой альтернативный подход для конкретных обстоятельств. Мне нужен был набор констант битовых масок на основе подзначений атрибутов (случаи, когда значение атрибута представляет собой массив или список значений). Он включает в себя эквивалент нескольких перекрывающихся перечислений.

Я создал класс для хранения и генерации значений битовой маски. Затем я могу использовать значения псевдопостоянной битовой маски таким образом, чтобы проверить, например, присутствует ли зеленый цвет в значении RGB:

if (value & Ez.G) {...}

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

class Ez {
constructor() {
    let rgba = ["R", "G", "B", "A"];
    let rgbm = rgba.slice();
    rgbm.push("M");              // for feColorMatrix values attribute
    this.createValues(rgba);
    this.createValues(["H", "S", "L"]);
    this.createValues([rgba, rgbm]);
    this.createValues([attX, attY, attW, attH]);
}
createValues(a) {                // a for array
    let i, j;
    if (isA(a[0])) {             // max 2 dimensions
        let k = 1;
        for (i of a[0]) {
            for (j of a[1]) {
                this[i + j] = k;
                k *= 2;
            }
        }
    }
    else {                       // 1D array is simple loop
        for (i = 0, j = 1; i < a.length; i++, j *= 2)
            this[a[i]] = j;
   }
}

2D-массив предназначен для атрибута SVG feColorMatrix values, который представляет собой матрицу 4x5 RGBA по RGBAM, где M - множитель. Результирующие свойства Ez - это Ez.RR, Ez.RG и т. д.

class Enum {
  constructor (...vals) {
    vals.forEach( val => {
      const CONSTANT = Symbol(val);
      Object.defineProperty(this, val.toUpperCase(), {
        get () {
          return CONSTANT;
        },
        set (val) {
          const enum_val = "CONSTANT";
          // generate TypeError associated with attempting to change the value of a constant
          enum_val = val;
        }
      });
    });
  }
}

Пример использования:

const COLORS = new Enum("red", "blue", "green");

Прочитал все ответы и не нашел непонятного и СУХОГО решения. Я использую этот однострочник:

const modes = ['DRAW', 'SCALE', 'DRAG'].reduce((o, v) => ({ ...o, [v]: v }), {});

он генерирует объект с удобочитаемыми значениями:

{
  DRAW: 'DRAW',
  SCALE: 'SCALE',
  DRAG: 'DRAG'
}

Справедливое решение, хотя вы потеряете синтаксические подсказки в редакторе кода

mattsven 16.04.2019 01:16

Спасибо! работал у меня после преобразования в простой javascript. Всем, кто ищет простое решение для javascript. var mappingTypesValues = ["ONE_TO_ONE" ,"ONE_TO_MANY"].reduce(function(o,v){o[v]= v;return o} , {});

jkb016 20.05.2019 22:52

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

Jack Giffin 11.07.2019 20:00

Меня не удовлетворил ни один из ответов, поэтому я сделал Еще одно перечисление (ДА!).

Эта реализация:

  • использует более современный JS
  • требуется только объявление этого одного класса, чтобы легко создавать перечисления
  • имеет отображение по имени (colors.RED), строке (colors["RED"]) и индексу (colors[0]), но вам нужно передать только строки в виде массива
  • связывает эквивалентные функции toString() и valueOf() с каждым объектом перечисления (если это почему-то нежелательно, его можно просто удалить - небольшие накладные расходы для JS)
  • имеет необязательное глобальное именование / хранение по строке имени
  • замораживает объект перечисления после создания, чтобы его нельзя было изменить

Особая благодарность Ответ Андре Фи за вдохновение.


Коды:

class Enums {
  static create({ name = undefined, items = [] }) {
    let newEnum = {};
    newEnum.length = items.length;
    newEnum.items = items;
    for (let itemIndex in items) {
      //Map by name.
      newEnum[items[itemIndex]] = parseInt(itemIndex, 10);
      //Map by index.
      newEnum[parseInt(itemIndex, 10)] = items[itemIndex];
    }
    newEnum.toString = Enums.enumToString.bind(newEnum);
    newEnum.valueOf = newEnum.toString;
    //Optional naming and global registration.
    if (name != undefined) {
      newEnum.name = name;
      Enums[name] = newEnum;
    }
    //Prevent modification of the enum object.
    Object.freeze(newEnum);
    return newEnum;
  }
  static enumToString() {
    return "Enum " +
      (this.name != undefined ? this.name + " " : "") +
      "[" + this.items.toString() + "]";
  }
}

Использование:

let colors = Enums.create({
  name: "COLORS",
  items: [ "RED", "GREEN", "BLUE", "PORPLE" ]
});

//Global access, if named.
Enums.COLORS;

colors.items; //Array(4) [ "RED", "GREEN", "BLUE", "PORPLE" ]
colors.length; //4

colors.RED; //0
colors.GREEN; //1
colors.BLUE; //2
colors.PORPLE; //3
colors[0]; //"RED"
colors[1]; //"GREEN"
colors[2]; //"BLUE"
colors[3]; //"PORPLE"

colors.toString(); //"Enum COLORS [RED,GREEN,BLUE,PORPLE]"

//Enum frozen, makes it a real enum.
colors.RED = 9001;
colors.RED; //0

Это может быть полезно:

const [CATS, DOGS, BIRDS] = ENUM();

Реализация проста и эффективна:

function * ENUM(count=1) { while(true) yield count++ }

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

@Carl Smith Я мог пропустить некоторые комментарии, но это довольно существенное изменение ?!

Bergi 24.08.2020 03:26

@Bergi, ты прав, но это является все тот же ответ. Я действительно только что сделал код для очистителя генератора и добавил пояснение, но вы правы, это довольно большая разница.

Carl Smith 24.08.2020 03:30
export const ButtonType = Object.freeze({ 
   DEFAULT: 'default', 
   BIG: 'big', 
   SMALL: 'small'
})

источник: https://medium.com/@idanlevi2/enum-in-javascript-5f2ff500f149

Пакет com.recoyxgroup.javascript.enum позволяет правильно определять классы перечисления и флаги перечисления, где:

  • Каждая константа представлена ​​как неизменный { _value: someNumber }.
  • Каждая константа имеет свойство, прикрепленное к классу перечисления (E.CONSTANT_NAME)
  • У каждой константы есть дружественная строка (constantName)
  • У каждой константы есть номер (someNumber)
  • Вы можете объявить пользовательские свойства / методы с помощью E.prototype.

Везде, где ожидается конкретное перечисление, соглашение должно делать E(v), например:

const { FlagsEnum } from 'com.recoyxgroup.javascript.enum';

const Rights = FlagsEnum('Rights', [
    'ADMINISTRATION',
    'REVIEW',
]);

function fn(rights) {
    rights = Rights(rights);
    console.info('administration' in rights, 'review' in rights);
}

fn( ['administration', 'review'] ); // true true
fn( 'administration' ); // true false
fn( undefined ); // false false

var r = Rights.ADMINISTRATION;
console.info( r == 'administration' );

Как видите, вы все еще можете сравнить значение со строкой.

Определения могут быть более конкретными:

const E = FlagsEnum('E', [
    ['Q', 0x10],
    ['K', 'someB'],
    ['L', [0x40, 'someL']],
]);

Продукты FlagsEnum> Свойства / методы экземпляра

  • число (должно было быть valueOf (), но из-за JS == должно было быть 'number')
  • набор()
  • исключать()
  • переключать()
  • фильтр()
  • значение()
  • нанизывать()

Обновление 05.11.2020:
Изменено, чтобы включить статические поля и методы для более точной репликации «истинного» поведения перечисления.

Кто-нибудь пробовал сделать это с классом, который содержит закрытые поля и методы доступа "get"? Я понимаю, что поля частных классов все еще являются экспериментальными на данный момент, но, похоже, они работают для создания класса с неизменяемыми полями / свойствами. Поддержка браузера тоже неплохая. Единственные «основные» браузеры, которые его не поддерживают, - это Firefox (я уверен, что они скоро появятся) и IE (кого это волнует).

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ:
Я не разработчик. Я просто искал ответ на этот вопрос и начал думать о том, как я иногда создаю «расширенные» перечисления в C#, создавая классы с частными полями и ограниченными средствами доступа к свойствам.

Образец класса

class Sizes {
    // Private Fields
    statiC#_SMALL = 0;
    statiC#_MEDIUM = 1;
    statiC#_LARGE = 2;

    // Accessors for "get" functions only (no "set" functions)
    static get SMALL() { return this.#_SMALL; }
    static get MEDIUM() { return this.#_MEDIUM; }
    static get LARGE() { return this.#_LARGE; }
}

Теперь у вас должна быть возможность напрямую вызывать свои перечисления.

Sizes.SMALL; // 0
Sizes.MEDIUM; // 1
Sizes.LARGE; // 2

Комбинация использования частных полей и ограниченных средств доступа означает, что значения перечисления хорошо защищены.

Sizes.SMALL = 10 // Sizes.SMALL is still 0
Sizes._SMALL = 10 // Sizes.SMALL is still 0
Sizes.#_SMALL = 10 // Sizes.SMALL is still 0

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

beruic 23.11.2020 13:24

@beruic Я бы сказал, что это более строго типизированный и подробный. Однако в коде нет двух элементов, выполняющих одну и ту же задачу, поэтому я бы сказал, что это определенно не является избыточным. Но, как я уже упоминал, я не разработчик, так что я знаю.

dsanchez 16.12.2020 04:32

В основном есть два типа перечислений: глобальные (например, C) и объектные (например, TypeScript). Для глобальных перечислений сделайте что-то вроде этого:

// Note that // enum is optional, though it makes it look slightly better.
const // enum
  SUNDAY = 1,
  MONDAY = 2,
  TUESDAY = 3,
  WEDNSDAY = 4,
  THURSDAY = 5,
  FRIDAY = 6,
  SATURDAY = 7;

А для объектных перечислений сделайте это (например, ответ Артура Чайки) -

// A trailing comma isn't required but is a good habit.
const Days = Object.freeze({
  SUNDAY = 1,
  MONDAY = 2,
  TUESDAY = 3,
  WEDSNDAY = 4,
  THURSDAY = 5,
  FRIDAY = 6,
  SATURDAY = 7,
});

или же

const Days = {
  SUNDAY = 1,
  MONDAY = 2,
  TUESDAY = 3,
  WEDSNDAY = 4,
  THURSDAY = 5,
  FRIDAY = 6,
  SATURDAY = 7,
};
Object.freeze(Days);

Первый способ объявления объектных перечислений выглядит немного чище. Кстати, глобальные и объектные перечисления - не совсем правильные термины, я их придумал.

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

Решение, сделанное (для глобальных перечислений) Aral Roca, выглядит потрясающе, но имеет недостаток в том, что он медленный (например, медленный на 0,1 секунды) -

function* ENUM(count = 1) {
  while (true) yield count++;
}

а потом

const [ RED, GREEN, BLUE ] = ENUM();

Вот мой взгляд на (отмеченный) завод Enum. Вот рабочая демонстрация.

/*
 * Notes: 
 * The proxy handler enables case insensitive property queries
 * BigInt is used to enable bitflag strings /w length > 52
*/
function EnumFactory() {
  const proxyfy = {
    construct(target, args) { 
      const caseInsensitiveHandler = { 
          get(target, key) {
          return target[key.toUpperCase()] || target[key];  
        } 
      };
      const proxified = new Proxy(new target(...args), caseInsensitiveHandler ); 
      return Object.freeze(proxified);
    },
  }
  const ProxiedEnumCtor = new Proxy(EnumCtor, proxyfy);
  const throwIf = (
      assertion = false, 
      message = `Unspecified error`, 
      ErrorType = Error ) => 
      assertion && (() => { throw new ErrorType(message); })();
  const hasFlag = (val, sub) => {
    throwIf(!val || !sub, "valueIn: missing parameters", RangeError);
    const andVal = (sub & val);
    return andVal !== BigInt(0) && andVal === val;
  };

  function EnumCtor(values) {
    throwIf(values.constructor !== Array || 
            values.length < 2 || 
        values.filter( v => v.constructor !== String ).length > 0,
      `EnumFactory: expected Array of at least 2 strings`, TypeError);
    const base = BigInt(1);
    this.NONE = BigInt(0);
    values.forEach( (v, i) => this[v.toUpperCase()] = base<<BigInt(i) );
  }

  EnumCtor.prototype = {
    get keys() { return Object.keys(this).slice(1); },
    subset(sub) {
      const arrayValues = this.keys;
      return new ProxiedEnumCtor(
        [...sub.toString(2)].reverse()
          .reduce( (acc, v, i) => ( +v < 1 ? acc : [...acc, arrayValues[i]] ), [] )
      );
    },
    getLabel(enumValue) {
      const tryLabel = Object.entries(this).find( value => value[1] === enumValue );
      return !enumValue || !tryLabel.length ? 
        "getLabel: no value parameter or value not in enum" :
        tryLabel.shift();
    },
    hasFlag(val, sub = this) { return hasFlag(val, sub); },
  };
  
  return arr => new ProxiedEnumCtor(arr);
}

Другие вопросы по теме