Каков предпочтительный синтаксис для определения перечислений в JavaScript? Что-то типа:
my.namespace.ColorEnum = {
RED : 0,
GREEN : 1,
BLUE : 2
}
// later on
if (currentColor == my.namespace.ColorEnum.RED) {
// whatever
}
Или есть более предпочтительная идиома?
@matsko - это не просто аргумент против использования ==?
0 == null возвращает false
Но false == 0 и +null == 0 (и преобразования в числа случаются иногда, когда вы этого не ожидаете), а также null == undefined и +undefined - это NaN (хотя и NaN != NaN).
Матрица двойного равенства сбивает с толку больше, чем автоматическое форматирование Microsoft Word.
Этот метод, который вы упомянули в своем вопросе, весьма полезен для NodeJS. Замена my.namespace.ColorEnum = на module.export = в пустом файле дает вам перечисление в собственном файле NodeJS. Блестяще.
Почему перед именем объекта не используется ключевое слово var?
@greener OP показывает, как он добавляет ColorEnum к объекту my.namespace "пространство имен" существующий.
Вы даже можете использовать символы в качестве значений ваших ключей, чтобы гарантировать уникальность, см. stackoverflow.com/questions/38993711/…
@David Citron Не стесняйтесь изменить принятый ответ на stackoverflow.com/a/5040502/3492994, что является лучшим способом создания перечислений в JavaScript.
На самом деле не так важно, как вы определяете константы перечисления. Суть перечислимых типов - гарантировать, что переменная имеет ограниченный набор допустимых значений, а переменные JS не имеют возможности сделать это. Существуют стандартные синтаксисы являются для определения типов перечислений в TypeScript и Flow, хотя это отличается от помещения констант перечисления в какую-то структуру, которую вы можете проверить во время выполнения.
@aaaaaa @matsko матрица двойного равенства не имеет особого значения, если вы привыкли всегда использовать тройное равенство, с единственным полезным исключением, что null == x - это true, если и только если x - это null или undefined. И если вы используете линтер, который помечает отрывочное (/ случайное) использование двойного равенства.
Вы говорите «+ null == 0», как будто это смысл использования triple =, @aaaaaa? Ну ... + null === 0, тоже ...



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это не очень хороший ответ, но я бы сказал, что это работает отлично, лично
Сказав это, поскольку не имеет значения, какие значения (вы использовали 0, 1, 2), я бы использовал значимую строку на случай, если вы когда-нибудь захотите вывести текущее значение.
Об этом говорилось в другом ответе, но, поскольку этот ответ является принятым, я опубликую его здесь. Решение OP правильное. Но будет еще лучше, если использовать с Object.freeze(). Это предотвратит изменение значений перечисления другим кодом. Пример: var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2});
@TolgaE, спасибо за эту библиотеку! Это вдохновило меня не только свести его к минимуму, но и добавить пару функций! Я раздвоил ваш и поместил все сюда: github.com/BlueHuskyStudios/Micro-JS-Enum
@Supuhstar Отлично! Я рад, что вы могли его использовать ... Не стесняйтесь сделать запрос на перенос, если вы хотите, чтобы он был объединен в этой библиотеке, тогда я могу обновить библиотеку npm
Если кому-то интересно, у меня есть типобезопасные перечисления реализовано, похожие на то, как они есть в Java. Это означает, что вы можете выполнять проверки instanceof. Например, ColorEnum.RED instanceof ColorEnum (возвращает true). Вы также можете разрешить экземпляр по имени ColorEnum.fromName("RED") === ColorEnum.RED (возвращает true). Каждый экземпляр также имеет методы .name() и .ordinal(), а само перечисление имеет метод values(), который возвращает массив всех констант.
Я не уверен, что согласен с предложением "содержательная строка". Перечисления не следует рассматривать как строки или числа; это абстрактные типы данных. Невозможно «вывести текущее значение» без какого-либо вспомогательного метода. В Java и .NET это метод ToString(). Мы, разработчики JS, уже слишком полагаемся на то, что «просто работает»! Кроме того, можно быстро получить switch по перечислению. Сравнение строк происходит медленнее, чем сравнение чисел, поэтому вы получите немного худшую производительность switch, если будете использовать строки вместо целых чисел.
@GovindRai Пожалуйста, ??? ??? ???????: прокси выдают исключение только при доступе к ним (есть ?? "???????-????" ??????), ужасно ???-?????????? и строго ????? ???????? ????. Для решения, которое решает все эти проблемы, см. stackoverflow.com/a/50355530/5601591
Итог: вы не можете.
Вы можете подделать это, но вы не получите безопасности типов. Обычно это делается путем создания простого словаря строковых значений, сопоставленных с целочисленными значениями. Например:
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?
Поэтому не сопоставляйте значения со свойствами объекта. Используйте геттер для доступа к перечислимому объекту (хранящемуся как свойство, скажем, «частного» объекта). Наивная реализация выглядела бы так - var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1
@ Скотт Эвернден: точка взята. @kangax: дело в том, что это все еще хак. Перечислений просто не существует в Javascript, точка, конец истории. Даже шаблон, предложенный Тимом Сильвестром, по-прежнему далеко не идеален.
Добавление в код литералов не очень удобно, поэтому имеет смысл создавать для него константы. Конечно, в Javascript тоже нет констант. По сути, это просто способ написать чистый код. Это невозможно принудительно, но в Javascript это не так. Вы можете переопределить константы, функции или что угодно еще. НАПРИМЕР: document.getElementById = function () {alert ("Вы облажались. Javascript небезопасен.");};
@Randolpho: А как насчет Object.freeze Артура Чайки? Разве это не помешает вам перенести с понедельника на четверг?
На практике я никогда не беспокоился о том, чтобы кто-то присваивал константу перечисления ... это прямо там с беспокойством о том, что кто-то может переопределить undefined. Ошибки, о которых стоит беспокоиться, - это недопустимые значения, присваиваемые переменной, которая должна быть перечислимым типом. Object.freeze не может с этим помочь.
Спасибо за все положительные отзывы, но я не думаю, что мой ответ ниже - лучший способ писать перечисления в 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: этот вариант использования на самом деле не имеет смысла для перечислений, поскольку вся их идея заключается в том, что вы заранее знаете все значения. Однако ничто не мешает вам добавить дополнительные значения позже в Javascript. Я добавлю пример этого к своему ответу.
+1 за ссылку на ваш пост с подходом свойств. Элегантно в том, что основные объявления просты, как в OP, с добавлением свойств при желании.
@Stijin, очень понравилось твое обновленное решение. Размещенный код в комментариях к вашему блогу и в комментариях ниже. В основном, используя функцию, выполните построение свойств из существующего списка хэшей и, возможно, заморозьте его (mkenum_2 в моем списке). Ваше здоровье.
Существует также библиотека, которая его реализует, а также включает полезные функции для сравнения и обратного поиска: github.com/adrai/enum
В своем сообщении в блоге вы упоминаете сериализацию как одну из причин, почему вам больше не нравится этот способ выполнения перечислений, но эту проблему можно решить, добавив метод toJSON (). Да, это еще один шаг к созданию перечисления, но вы, вероятно, могли бы сделать это как миксин.
@KevinWiskia Да, метод toJSON поможет вам на полпути. Но вам также понадобится reviver для десериализации ... И этот восстановитель должен быть передан на JSON.parse ... Так что вы не можете просто добавить fromJSON или что-то в этом роде и покончить с этим. Вам нужно следить за тем, чтобы эта функция оживления использовалась всякий раз, когда JSON может содержать ваше причудливое перечисление ... Так что это значительно усложняет работу.
Начиная с версии 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.
Это правильный ответ сейчас, в 2012 году. Проще: var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });. Вам не нужно указывать идентификатор, вы можете просто использовать пустой объект для сравнения перечислений. if (incommingEnum === DaysEnum.monday) //incommingEnum is monday
Для обратной совместимости if (Object.freeze) { Object.freeze(DaysEnum); }
Я хотел бы указать, что выполнение ({ monday: {}, и т. д. Означает, что если вы конвертируете этот объект в JSON с помощью stringify, вы получите [{"day": {}}], который не будет работать.
@jcollum: Вы можете объяснить? Почему бы stringify не найти имена «понедельник», «вторник» и т. д.? И откуда взялась эта «дневная» струна? Или ваш комментарий относится к квадратным скобкам ([и])?
@StijndeWitt Я почти уверен, что stringify превращает объект в "propname": "propvalue" - в этом случае значение prop является пустым объектом (если входящий объект выглядит как {day: DaysEnum.monday}).
@StijndeWitt Я в конечном итоге использовал эту схему перечисления, просто использовал что-то вроде {monday: "m", tuesday: "t"}, чтобы я мог отправлять значения перечисления по проводу.
@jcollum: Я бы подумал, что это будет странно. Что делает stringify, когда вы отправляете ему объект с атрибутами, которые не являются строками или числами, а являются массивами или объектами? Он должен был бы закодировать их как «имя»: [] или «имя»: {} верно? Я не уверен, что это действительно проблема, пока не увижу пример :)
@StijndeWitt jsfiddle.net/jcollum/eesAN/4 вам будет сложно превратить фокстрот обратно в alpha.a после преобразования в строку
@jcollum: Хорошо, теперь я получил ваш пример. Итак, вы говорите, что «перечисления», значения которых являются объектами (независимо от того, заморожены они или нет), нельзя сериализовать туда и обратно с помощью stringify, потому что впоследствии они не пройдут проверку личности? Так что, если вам это нужно, вы должны либо использовать числа или другие «объекты-значения», либо реализовать настраиваемую строку ... Но действительно очень хороший момент, я никогда не думал об этом раньше.
@saluce не означает ли это, что в IE могут быть ошибки, которых не было бы в остальной части браузера? Кажется опасным использовать это, так как он не будет работать до IE 9, поэтому у вас будет хороший случай для конкретных ошибок IE :(, грустно, глядя на совместимость, FF 4, Chrome 6, IE 9: O
@John Вы можете определить Object.freeze как проходной без операций, если он не существует. Это не решит проблему, но сделает решение несколько совместимым.
Если вы действительно хотите использовать номер, вы можете получить его с помощью DaysEnum.monday.toString().
Насколько я понимаю, это правильный ответ. Это просто и легко читать. Однако, что наиболее важно, это не позволяет другим ошибаться со значениями перечисления, которое является важным свойством перечислений в языках, которые их поддерживают изначально.
@GabrielLlamas Иногда, тем не менее, вам нужен порядковый номер перечисляемой константы.
@Supuhstar Мое мнение по этому вопросу сейчас другое. Не используйте freeze (), это совершенно бесполезно и пустая трата времени на «глупые» вещи. Если вы хотите раскрыть перечисление, просто сделайте это: var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}. Сравнение объектов, как в моем предыдущем комментарии, НАМНОГО МЕДЛЕЕ, чем сравнение чисел.
@Supuhstar - Использование порядкового номера выходит за рамки цели структуры перечисления. Перечисления должны строго использоваться для равенства. Установка значений для пустых объектов может быть медленнее, но это предотвращает неправильное использование перечисления - например, с порядковыми числами у разработчика возникнет соблазн использовать перечисление для сортировки. Лучше подойдет другая конструкция.
Проблема, которую я заметил при выполнении: ({понедельник: {}, вторник: {} и т. д., Заключалась в том, что при тестировании вы не получали очень полезных сообщений, когда он терпит неудачу. Например: AssertionError: ожидалось, что {} будет равно {}
@CasBloem const по-прежнему позволит вам изменять значения объекта.
@GabrielLlamas сравнивает объекты с === довольно быстро. Теоретически это должно быть примерно таким же, как сравнение двух чисел, потому что оно сводится к проверке равенства двух значений указателя (целых чисел).
@Andy Это вдвое сложнее сравнения двух чисел (иногда тройных), потому что браузер также должен проверять тип. Конечно, конечно, браузер может попытаться угадать тип и обычно оказывается прав. Но никто не может знать наверняка, потому что нам еще предстоит избавиться от злого метода eval (var innocentInt = 0, bigObject = {}; eval("innocentInt = bigObject");).
Разве браузеру не нужно проверять тип сравниваемых переменных, являются ли они числами, объектами, строками и т. д.?
@ Энди Нет. Это не так. Например, 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 делают
@GovindRai Пожалуйста, ??? ??? ???????: прокси выдают исключение только при доступе к ним (есть ?? "???????-????" ??????), ужасно ???-?????????? и строго ????? ???????? ????. Для решения, которое решает все эти проблемы, см. stackoverflow.com/a/50355530/5601591
Что именно делает замораживание?
@AaronFranke См. Связанный источник: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
@AaronFranke ?????? ?? ???????? ??? ??????????? при таком использовании! Поиск перечислений для объектов выполняется медленно даже с JIT. Для максимальной производительности вам нужен var b=2 в минифицированном выводе, а не var b=a.tuesday (где a - это DaysEnum, а b - это день). Однако ввод чисел в исходный код затрудняет / делает невозможным чтение, поддержку и передачу. Компилятор закрытия с ADVANCED_OPTIMIZATIONS может вставлять переменные в виде чисел. Однако он не может быть встроен при использовании Object.freeze. Подробнее см. stackoverflow.com/a/50355530/5601591.
@JackGiffin, часть вашего ответа, связанная с производительностью, не так ясна. Есть давно обновленный ответ на другой вопрос, который показывает, что разницы в производительности практически нет: stackoverflow.com/a/23198265/572370. Я бы сказал, все зависит от конкретного варианта использования и каждый должен проверить свою производительность.
@ArturCzajka Я возражаю не в том, что замороженные объекты плохо работают, а в том, что Object.freeze мешает производительности минифицированного производственного кода. Посетите эта страница. С заморозкой это alert(a.a). Без него это alert(1).
Вот чего мы все хотим:
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 (и OP) Я создал улучшенную версию на основе этого ответа: stackoverflow.com/a/60309416/1599699
@Andrew Я создал отдельный и гораздо более продуманный, тщательно продуманный и тщательно проверенный ответ, который я много раз использовал в продакшене: stackoverflow.com/a/50355530/5601591
Если вы используете Магистральная сеть, вы можете бесплатно получить полнофункциональную функцию перечисления (поиск по идентификатору, имени, настраиваемым членам) с помощью 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 метода для сопоставления перечислений из объектов сообщений.
Некоторые преимущества этого подхода:
Некоторые недостатки:
Я модифицировал решение Андре Фи:
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 ...
и, конечно, если вы действительно ИСПОЛЬЗУЕТЕ Typescript: enum MyEnum { Foo, Bar, Foobar }
Я играл с этим, так как мне нравятся мои перечисления. знак равно
Используя 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');
Я не учел этого, потому что это не мой нормальный метод ведения дел. Но вы абсолютно правы! Я отредактирую это в.
Мне это очень нравится, хотя я не большой поклонник мусора в пространстве объектов (с глобальной функцией ENUM). Преобразовал это в функцию mkenum и добавил необязательные числовые присвоения => var mixedUp = mkenum ('ЧЕРНЫЙ', {КРАСНЫЙ: 0x0F00, СИНИЙ: 0X0F, ЗЕЛЕНЫЙ: 0x0F0, БЕЛЫЙ: 0x0FFF, ONE: 1}, ДВА, ТРИ, ЧЕТЫРЕ) ; // Добавляем мой код в качестве ответа ниже. Спасибо.
Если честно, я этим больше даже не пользуюсь. Я использовал компилятор Google Closure Compiler, и он не работает слишком хорошо (или просто усложняет ситуацию), если вы используете расширенную настройку. Итак, я только что вернулся к стандартной объектной нотации.
false используется по умолчанию для writable, enumerable и configurable. Не нужно пережевывать настройки по умолчанию.
Я сделал это некоторое время назад, используя смесь __defineGetter__ и __defineSetter__ или defineProperty, в зависимости от версии JS.
Вот созданная мной функция генерации перечисления: https://gist.github.com/gfarrell/6716853
Вы бы использовали это так:
var Colours = Enum('RED', 'GREEN', 'BLUE');
И это создаст неизменную строку: int dictionary (enum).
Хорошо, я переписал его, и теперь он не зависит от библиотеки (хотя он предполагает какой-то загрузчик AMD, но его можно удалить).
Быстрый и простой способ:
var Colors = function(){
return {
'WHITE':0,
'BLACK':1,
'RED':2,
'GREEN':3
}
}();
console.info(Colors.WHITE) //this prints out "0"
Функция не нужна и дает тот же результат, что и OP.
Вы также можете попробовать определить новую функцию и, следовательно, новое пространство имен, и добавить к нему переменные, как это.
function Color () {};
Color.RED = 1;
Color.YELLOW = 2;
Пока кто-нибудь использует пространство имен, предоставленное функцией Color, все будет хорошо. Если вы знаете Java, это что-то вроде старых перечислений: где мы используем класс или интерфейс только для хранения статических атрибутов. Если функция в javascript является своего рода классом, это почти такой же подход.
Я считаю, что очень простой способ определить перечисления.
Надеюсь, это поможет!
Привет.
Виктор.
Этот ответ неверен. Когда люди говорят об использовании функций для имитации пространств имен, они говорят об использовании функций для создания замыканий, которые остаются прикрепленными к объекту, который возвращается этой функцией после завершения исходной функции. Ваш пример не делает этого. Ваш пример просто привязывает свойства к объекту, который также является функцией. По сути, ваш пример функционально совпадает с тем, что опубликовал OP.
я был скорее прагматичным, чем теоретическим. Я как раз отвечал на вопрос, и для ТОЛЬКО определения констант нет особой пользы от знания большего, чем это. Имя нужно помнить всегда, всегда.
Добавление настраиваемых свойств к функциям - плохая практика и не очень эффективна.
@Sildoreth (то же самое, что и Джек) в современном машинописном тексте делает такие вещи «за кулисами для нас» (я имею в виду, что переведенный код делает такие вещи, как добавление свойства в функцию для реализации статических членов класса (для ES5)), поэтому я не понимаю, почему это не рекомендуется?
очень стоит упомянуть, как машинописный текст переводит перечисления ... это может быть полезно для понимания того, какой подход рекомендуется для написания перечислений
На момент написания Октябрь 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.
Еще раз, если вы просто случайный пользователь или работаете в браузере и т. д., Просто возьмите заводскую часть кода, потенциально удалив библиотеку подчеркивания, если вы не хотите использовать ее в своем коде.
Не могли бы вы опубликовать ответ просто с фразой «вот как это сделать, если вы обычный пользователь, которому нужны только перечисления, а не фабрики, символы подчеркивания или что-нибудь необычное»?
Несмотря на то, что это довольно круто с точки зрения разработчиков, оно не очень чистое и читабельное. Решение Enum из OP проще и читабельнее во всех отношениях, поэтому его лучше использовать. Тем не менее, довольно круто, что вы это придумали.
ваши ответы слишком сложны
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++. Пожалуйста, уважайте другие ответы и разработчиков, которые могут предпочесть этот ответ вашему.
Мне действительно нравится то, что сделал @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: {}
}
Вам не нужно следить за тем, чтобы таким образом вы не присваивали повторяющиеся числа разным значениям перечисления. Создается экземпляр нового объекта, которому присваиваются все значения перечисления.
Этот ответ недооценен. Это одна из моих любимых идей из-за своей простоты. На практике я думаю, что буду придерживаться строк, потому что сейчас их легче отлаживать.
Хм, только убедитесь, что этот код не вызывается дважды ...
Вы можете использовать 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()!
Пожалуйста, попробуйте это: if (JSON.parse(JSON.stringify(DaysEnum.monday)) != DaysEnum.monday) console.error('Oops!'). Прочтите мое сообщение в блоге Перечисления в Javascript, посвященное именно этой проблеме, чтобы узнать, почему это происходит.
В большинстве современных браузеров есть примитивный тип данных условное обозначение, который можно использовать для создания перечисления. Это обеспечит типобезопасность перечисления, поскольку JavaScript гарантирует уникальность каждого значения символа, то есть Symbol() != Symbol(). Например:
const COLOR = Object.freeze({RED: Symbol(), BLUE: Symbol()});
Чтобы упростить отладку, вы можете добавить описание к значениям перечисления:
const COLOR = Object.freeze({RED: Symbol("RED"), BLUE: Symbol("BLUE")});
На 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 года далеко не достаточно. Еще не готово к производству.
Хотя поддержки браузера еще нет, это лучший ответ, поскольку он близок к тому, для чего предназначен Symbol.
Ммм ... значения перечисления часто должны быть сериализуемыми, а символы не так удобны для сериализации и десериализации.
Это только у меня, или Object.freeze только для людей, которые не приняли тот факт, что «обезьяны на свой страх и риск» - это общественный договор JS?
@Andy да сериализация раздражает. Я закончил тем, что сделал явный toJSON для содержащего класса, чтобы использовать этот подход: stackoverflow.com/questions/58499828/…
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.
Пожалуйста, не используйте Object.freeze. Это не позволяет компилятору Closure встраивать объект.
Думаю, им легко пользоваться. 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); }
Я написал 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 в основном заводской.
Полностью задокументированное руководство доступно здесь. Надеюсь, это поможет.
Это не совсем перечисление. Но я вижу, что это очень полезно.
@Will, как бы вы их назвали ^^? AugmentedEnum?
Я не уверен ... Это своего рода смесь перечислений + метаданных (в вашем примере). У перечисления просто есть имя и значение структуры, в которое оно может быть приведено или из которого. Вы можете разместить неограниченное количество свойств для каждого, используя это. Я видел этот шаблон раньше, и команда, написавшая его, назвала константы результата ... Включила отдельные типы констант, такие как DataType, у которых не только было имя, но и значение идентификатора, допустимые операторы (еще одна группа констант! ), допустимые преобразования и т. д. Действительно полезно, но гораздо больше, чем простое перечисление.
Очень плохой исполнитель. Делает весь код медленнее. Увеличивает минифицированный код в несколько раз. Очень плохой.
Я создал класс 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, так как он заморожен). Тем не менее, приведенный ниже более усовершенствованный код предлагает следующие преимущества:
CellStateфункция 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;
Хороший пример :-)
Решение Alien - максимально упростить задачу:
Если ключевое слово 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 {/*....*/}
Не могли бы вы объяснить, почему наличие родительского класса является преимуществом? Я чувствую, что что-то упускаю!
Не делай этого. new ColorEnum() не имеет абсолютно никакого смысла.
расширение перечисления звучит безумно, правда
если язык не поддерживает его изначально, имеет смысл сохранить это соглашение и использовать его так! я согласен!
Я думаю (?), К чему стремится OP: Преимущество чистой статики в том, что она доступна повсюду как синглтон, и вы не необходимость для создания экземпляра класса - OP не предлагает, чтобы вы это сделали! Я думаю, он говорит о том, что суперкласс Enum имеет стандартные методы перечислителя статический, такие как getValues(), getNames(), iterate() и т. д. В этом случае вам не нужно заново реализовывать их для каждого нового типа enum.
Вы можете попробовать использовать 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(', ') }
}
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 вы можете переопределить метод valueOf по умолчанию, указав его как метод экземпляра в классе Enum. Однако почему вы хотите получить к нему доступ именно таким образом, а не просто получить к нему доступ через точечную нотацию?
Мое перечисление - const logLevelEnum = new Enum ({INFO: "info", DEBUG: "debug"}), и я анализирую из ввода произвольную строку "info" или "debug". Поэтому мне нужно что-то вроде currentLogLevel = logLevelEnum.parseOrThrow (settings.get ("log_level"))
Почему ты просто не мог сделать logLevelEnum[settings.get("log_level")]? добавление parseOrThrow будет повторением того, что ловушки прокси уже делают для вас.
Вот как 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'
}
Справедливое решение, хотя вы потеряете синтаксические подсказки в редакторе кода
Спасибо! работал у меня после преобразования в простой javascript. Всем, кто ищет простое решение для javascript. var mappingTypesValues = ["ONE_TO_ONE" ,"ONE_TO_MANY"].reduce(function(o,v){o[v]= v;return o} , {});
Я добавил забавные красочные изображения в мой ответ здесь. Как вы думаете, теперь он может быть немного менее сухим?
Меня не удовлетворил ни один из ответов, поэтому я сделал Еще одно перечисление (ДА!).
Эта реализация:
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, ты прав, но это является все тот же ответ. Я действительно только что сделал код для очистителя генератора и добавил пояснение, но вы правы, это довольно большая разница.
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 }.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> Свойства / методы экземпляра
Обновление 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 Я бы сказал, что это более строго типизированный и подробный. Однако в коде нет двух элементов, выполняющих одну и ту же задачу, поэтому я бы сказал, что это определенно не является избыточным. Но, как я уже упоминал, я не разработчик, так что я знаю.
В основном есть два типа перечислений: глобальные (например, 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);
}
Не используйте
0в качестве номера перечисления. Если только он не используется для чего-то, что не было установлено. JS рассматриваетfalse || undefined || null || 0 || "" || '' || NaNкак одно и то же значение при сравнении с==.