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

avatar
David Citron
13 ноября 2008 в 19:09
1192701
51
2285

Будет ли следующее заставит объекты соответствовать всем характеристикам, которые имеют перечисления в JavaScript? Примерно так:

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

// later on

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

Или есть другой способ сделать это?

Источник
matsko
17 января 2015 в 18:10
167

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

sdm350
24 февраля 2015 в 21:40
200

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

mcont
3 апреля 2015 в 14:58
8

0 == null возвращает false

sanderd17
30 мая 2015 в 15:59
14

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

aaaaaa
23 марта 2016 в 20:32
71

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

Tim Visée
12 апреля 2016 в 23:09
2

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

Greener
3 июня 2016 в 21:49
0

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

Stijn de Witt
30 июня 2016 в 00:40
2

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

Simone Poggi
17 августа 2016 в 10:12
2

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

Stefnotch
10 января 2018 в 18:43
1

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

Andy
19 декабря 2018 в 01:59
0

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

Andy
19 декабря 2018 в 02:00
0

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

vbullinger
8 февраля 2019 в 20:19
2

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

Ответы (51)

avatar
Artur Czajka
18 февраля 2011 в 11:03
1122

Начиная с версии 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

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

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

Artur Czajka
15 марта 2012 в 11:05
6

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

Gabriel Llamas
7 апреля 2012 в 10:29
84

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

saluce
24 августа 2012 в 15:56
36

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

jcollum
1 февраля 2013 в 00:20
19

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

Stijn de Witt
1 февраля 2013 в 19:56
0

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

jcollum
1 февраля 2013 в 22:47
2

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

jcollum
1 февраля 2013 в 22:48
0

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

Stijn de Witt
4 февраля 2013 в 14:52
0

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

jcollum
4 февраля 2013 в 17:24
2

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

Stijn de Witt
19 февраля 2013 в 14:13
1

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

John
27 февраля 2013 в 22:10
0

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

Artur Czajka
4 марта 2013 в 06:00
0

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

Dan
19 марта 2013 в 19:32
0

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

Sildoreth
16 января 2014 в 19:23
2

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

Ky Leggiero
9 апреля 2014 в 05:35
0

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

Gabriel Llamas
9 апреля 2014 в 07:58
13

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

aaaaaa
23 марта 2016 в 19:28
2

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

Julian Mann
20 августа 2016 в 23:09
5

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

George
12 апреля 2018 в 13:21
0

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

Andy
25 июля 2018 в 00:15
1

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

Jack Giffin
26 июля 2018 в 00:29
0

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

Andy
27 июля 2018 в 01:26
0

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

Jack Giffin
28 ноября 2018 в 00:34
1

@ Энди Нет. Это не так. Например, 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 делают

Govind Rai
11 декабря 2018 в 23:10
2

Обновление ES6: используйте прокси-серверы JavaScript для создания класса Enum. Выдавать ошибки времени компиляции при попытке доступа к несуществующим счетчикам или добавлению / обновлению счетчиков - точно так же, как традиционные перечисления. 15 строк кода .coderhelper.com/a/49309248/2757916

Jack Giffin
10 июля 2019 в 18:06
3

@GovindRai Пожалуйста, 𝗻𝗼𝘁 𝘂𝘀𝗲 𝗽𝗿𝗼𝘅𝗶𝗲𝘀: прокси выдают исключение только при доступе к ним (есть 𝗻𝗼 "𝗰𝗼𝗺𝗽𝗶𝗹𝗲-𝘁𝗶𝗺𝗲" 𝗲𝗿𝗿𝗼𝗿𝘀), ужасно 𝗶𝗹𝗹-𝗽𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝘁 и строго 𝗯𝗹𝗼𝗮𝘁 𝗺𝗶𝗻𝗶𝗳𝗶𝗲𝗱 𝘀𝗶𝘇𝗲. Решение, которое решает все эти проблемы, см. На coderhelper.com/a/50355530/5601591.

Aaron Franke
6 декабря 2019 в 04:23
0

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

Artur Czajka
9 декабря 2019 в 00:28
0

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

Jack Giffin
22 февраля 2020 в 18:22
1

@AaronFranke 𝗙𝗿𝗲𝗲𝘇𝗲 𝗶𝘀 𝗵𝗼𝗿𝗿𝗶𝗯𝗹𝗲 𝗳𝗼𝗿 𝗽𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲 при таком использовании! Поиск перечислений для объектов выполняется медленно даже при использовании JIT. Для максимальной производительности в минифицированном выводе требуется var b=2, а не var b=a.tuesday (где a - DaysEnum, а b - день). Однако ввод чисел в исходный код делает его трудным / невозможным для чтения, поддержки и передачи. Компилятор Closure с ADVANCED_OPTIMIZATIONS может вставлять переменные в виде чисел. Однако он не может быть встроен при использовании Object.freeze. См. Coderhelper.com/a/50355530/5601591 для получения дополнительной информации.

Artur Czajka
24 февраля 2020 в 13:30
0

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

Jack Giffin
25 февраля 2020 в 10:40
1

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

avatar
Nirvana
16 апреля 2021 в 03:31
0

Примечание: этот ответ устарел. Вы должны увидеть мой обновленный ответ

Я опаздываю, но в C / Java перечисления - это не словарь с числовыми значениями, а классы. Моя попытка подражать этому:

function Enum(vals) {
  vals = vals.map((el) => ""+el);
  function T(value) {
    value = ""+value;
    if(vals.indexOf(value) === -1) {
         throw new Error("invalid enum value");
    }
    
    this.value = value;
  }
   

  T.prototype.toString = function() { return this.value; }
  T.values = function () { return vals; }

  for(var i = 0; i < vals.length; i++) {
    T[vals[i]] = new T(vals[i]);
  } 

  Object.freeze(T);

  return T;
 }

//Important: DO NOT use "new"!
var Colors = Enum(["RED","GREEN","BLUE"]);

//Changing a static property no effect...
Colors.RED = "something";

//...But you can still add to prototype
Colors.prototype.something = 100;
console.log(Colors.RED);
avatar
KooiInc
1 ноября 2020 в 11:30
0

Вот мой взгляд на (помеченный) завод 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);
}
avatar
Novachief
27 октября 2020 в 01:16
1

Существует два основных типа перечислений: глобальные (например, 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);

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

Изменить:

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

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

, а затем

const [ RED, GREEN, BLUE ] = ENUM();
avatar
dsanchez
18 октября 2020 в 17:45
3

Обновление 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
beruic
23 ноября 2020 в 10:24
0

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

dsanchez
16 декабря 2020 в 01:32
0

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

avatar
Klaider
26 августа 2020 в 22:19
0

Пакет 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.log('administration' in rights, 'review' in rights);
}

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

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

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

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

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

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

  • число (должно было быть valueOf (), но из-за JS == должно было быть 'number')
  • набор ()
  • исключить ()
  • переключатель ()
  • фильтр ()
  • valueOf ()
  • toString ()
avatar
Idan
16 июля 2020 в 07:41
0
export const ButtonType = Object.freeze({ 
   DEFAULT: 'default', 
   BIG: 'big', 
   SMALL: 'small'
})

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

avatar
Aral Roca
13 июня 2020 в 10:06
5

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

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

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

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

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

Bergi
24 августа 2020 в 00:26
1

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

Carl Smith
24 августа 2020 в 00:30
2

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

avatar
Andrew
19 февраля 2020 в 21:21
7

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

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

  • использует более современный 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
avatar
oluckyman
15 апреля 2019 в 19:16
1

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

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

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

{
  DRAW: 'DRAW',
  SCALE: 'SCALE',
  DRAG: 'DRAG'
}
mattsven
15 апреля 2019 в 22:16
0

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

jkb016
20 мая 2019 в 19:52
0

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

Jack Giffin
11 июля 2019 в 17:00
2

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

avatar
papiro
19 сентября 2018 в 15:11
3
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");
avatar
jamess
25 августа 2018 в 17:27
0

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

Я создал класс для хранения и генерации значений битовой маски. Затем я могу использовать значения псевдопостоянной битовой маски таким образом, чтобы проверить, например, присутствует ли зеленый в значении 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 и т. Д.

avatar
Jack Giffin
15 мая 2018 в 16:53
40

??????????????? - ???????????????????????????? ?????????????? ??????????????????????????????????????? ??????????????????????????????? ????????????????????

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


Underscore-Notation Variables

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

  1. Определите имя для группы перечисления. Подумайте о существительном, которое может описать цель перечисления или, по крайней мере, записи в перечислении. Например, группу перечислений, представляющих цвета, выбираемые пользователем, лучше назвать ЦВЕТАМИ, чем ЦВЕТАМИ.
  2. Решите, являются ли перечисления в группе взаимоисключающими или независимыми. Если они взаимоисключающие, начинайте каждое имя перечисляемой переменной с ENUM_. Если независимые или параллельные, используйте INDEX_.
  3. Для каждой записи создайте новую локальную переменную, имя которой начинается с ENUM_ или INDEX_, затем имя группы, затем подчеркивание, а затем уникальное понятное имя для свойства
  4. Добавьте в самом конце перечислимую переменную ENUMLENGTH_, ENUMLEN_, INDEXLENGTH_ или INDEXLEN_ (независимо от того, является ли LEN_ или LENGTH_ личным предпочтением). Вы должны использовать эту переменную везде, где это возможно, в своем коде, чтобы гарантировать, что добавление дополнительной записи в перечисление и увеличение этого значения не нарушат ваш код.
  5. Присвойте каждой последовательной перечисляемой переменной значение, на единицу большее, чем предыдущее, начиная с 0. На этой странице есть комментарии, в которых говорится, что 0 не следует использовать в качестве перечисляемого значения, потому что 0 == null, 0 == false, 0 == "" и прочая JS безумие. Я утверждаю, что, чтобы избежать этой проблемы и одновременно повысить производительность, всегда используйте === и никогда не позволяйте == появляться в вашем коде, кроме как typeof (например, typeof X == "string"). За все годы использования === у меня ни разу не было проблем с использованием 0 в качестве значения перечисления. Если вы по-прежнему брезгливы, то во многих случаях 1 можно использовать в качестве начального значения в перечислениях ENUM_ (но не в перечислениях INDEX_) без потери производительности.
const ENUM_COLORENUM_RED   = 0;
const ENUM_COLORENUM_GREEN = 1;
const ENUM_COLORENUM_BLUE  = 2;
const ENUMLEN_COLORENUM    = 3;

// later on

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

Вот как я помню, когда использовать INDEX_, а когда использовать ENUM_:

// Precondition: var arr = []; //
arr[INDEX_] = ENUM_;

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

const ENUM_PET_CAT = 0,
      ENUM_PET_DOG = 1,
      ENUM_PET_RAT = 2,
      ENUMLEN_PET  = 3;

var favoritePets = [ENUM_PET_CAT, ENUM_PET_DOG, ENUM_PET_RAT,
                    ENUM_PET_DOG, ENUM_PET_DOG, ENUM_PET_CAT,
                    ENUM_PET_RAT, ENUM_PET_CAT, ENUM_PET_DOG];

var petsFrequency = [];

for (var i=0; i<ENUMLEN_PET; i=i+1|0)
  petsFrequency[i] = 0;

for (var i=0, len=favoritePets.length|0, petId=0; i<len; i=i+1|0)
  petsFrequency[petId = favoritePets[i]|0] = (petsFrequency[petId]|0) + 1|0;

console.log({
    "cat": petsFrequency[ENUM_PET_CAT],
    "dog": petsFrequency[ENUM_PET_DOG],
    "rat": petsFrequency[ENUM_PET_RAT]
});

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


??????????????????????? ??????????????????????????????????? ??????????????????????????????????? ???????????????? ???????????????????????????????

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

Addition extension diagram

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

    // use INDEX_ when representing the index in an array instance
    const INDEX_PIXELCOLOR_TYPE = 0, // is a ENUM_PIXELTYPE
          INDEXLEN_PIXELCOLOR   = 1,
          INDEX_SOLIDCOLOR_R    = INDEXLEN_PIXELCOLOR+0,
          INDEX_SOLIDCOLOR_G    = INDEXLEN_PIXELCOLOR+1,
          INDEX_SOLIDCOLOR_B    = INDEXLEN_PIXELCOLOR+2,
          INDEXLEN_SOLIDCOLOR   = INDEXLEN_PIXELCOLOR+3,
          INDEX_ALPHACOLOR_R    = INDEXLEN_PIXELCOLOR+0,
          INDEX_ALPHACOLOR_G    = INDEXLEN_PIXELCOLOR+1,
          INDEX_ALPHACOLOR_B    = INDEXLEN_PIXELCOLOR+2,
          INDEX_ALPHACOLOR_A    = INDEXLEN_PIXELCOLOR+3,
          INDEXLEN_ALPHACOLOR   = INDEXLEN_PIXELCOLOR+4,
    // use ENUM_ when representing a mutually-exclusive species or type
          ENUM_PIXELTYPE_SOLID = 0,
          ENUM_PIXELTYPE_ALPHA = 1,
          ENUM_PIXELTYPE_UNKNOWN = 2,
          ENUMLEN_PIXELTYPE    = 2;

    function parseHexColor(inputString) {
        var rawstr = inputString.trim().substring(1);
        var result = [];
        if (rawstr.length === 8) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
            result[INDEX_ALPHACOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[INDEX_ALPHACOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[INDEX_ALPHACOLOR_B] = parseInt(rawstr.substring(4,6), 16);
            result[INDEX_ALPHACOLOR_A] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 4) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
            result[INDEX_ALPHACOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[INDEX_ALPHACOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[INDEX_ALPHACOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
            result[INDEX_ALPHACOLOR_A] = parseInt(rawstr[3], 16) * 0x11;
        } else if (rawstr.length === 6) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[INDEX_SOLIDCOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[INDEX_SOLIDCOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[INDEX_SOLIDCOLOR_B] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 3) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[INDEX_SOLIDCOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[INDEX_SOLIDCOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[INDEX_SOLIDCOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
        } else {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_UNKNOWN;
        }
        return result;
    }

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

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

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


???????????????????????? ???????????????? ??????????????????????????? ???????????????????????????????

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

Wringing code via Closure Compiler

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

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

Closure Compiler вознаграждает вас за более умное кодирование и хорошую организацию кода, потому что, в то время как многие минификаторы наказывают организованный код большим уменьшенным размером файла, Closure Compiler может просеять всю вашу чистоту и здравомыслие, чтобы вывести файл еще меньшего размера, если вы используете такие приемы, как перечисление имен переменных. Это, по нашему мнению, является святым Граалем кодирования: инструмент, который одновременно помогает вашему коду с уменьшенным миниатюрным размером и помогает вашему разуму, тренируя лучшие навыки программирования.


??????????????????????????? ???????????????? ????????????????

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

Источник без использования перечислений (длина: 1973 байта (477 байтов короче, чем числовой код!) > длиннее числового кода ))

Chart of code sizes



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


????????????????????????????????????????? ???? ???????????? ???????????????????????

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

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

// later on

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

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

// later on

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

??????????????????????????????? ???????????????????????????????????????????

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

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

/// Hashmaps are slow, even with JIT juice
var ref = {};
ref.count = 10;
ref.value = "foobar";

Сравните код выше с кодом ниже.

/// Arrays, however, are always lightning fast
const INDEX_REFERENCE_COUNT = 0;
const INDEX_REFERENCE_VALUE = 1;
const INDEXLENGTH_REFERENCE = 2;

var ref = [];
ref[INDEX_REFERENCE_COUNT] = 10;
ref[INDEX_REFERENCE_VALUE] = "foobar";

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

/// Hashmaps are slow, even with JIT juice
var a={count:10,value:"foobar"};

Минимизированный код без перечислений вверху, а минимизированный код с перечислениями внизу.

/// Arrays, however, are always lightning fast
var a=[10,"foobar"];

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


???????????????? ???????????????????????????????????

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

CodeMirror highlighting demonstration

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

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

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

В приведенном выше фрагменте вы получили сообщение об ошибке, потому что ENUM_COLORENUM_DNE не существует.


??????????????????????????????????????

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

Andrew
19 февраля 2020 в 20:17
0

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

Jack Giffin
19 февраля 2020 в 23:02
1

@ Андрей. На мой ответ, вы можете получить и то, и другое. Мой ответ дает самый простой в использовании / управлении код и наименьший размер минимизированного кода.

Andrew
20 февраля 2020 в 16:54
1

Мой ответ (новый) намного проще в использовании и управлении. (Чтобы сэкономить время: coderhelper.com/a/60309416/1599699)

Jack Giffin
22 февраля 2020 в 16:56
2

@Andrew Я попытался применить ваше Еще одно перечисление (ДА!) к примеру синтаксического анализатора цвета в моем ответе. Однако я обнаружил несколько проблем, которые вы, возможно, захотите решить. YEA не имеет возможности расширять перечисления подклассами, что вынуждает меня создавать отдельные родительские и дочерние классы, что может быть довольно сложно управлять в больших проектах. Да не гарантирует, что запись существует (например, colors.REED дает undefined), поэтому опечатки создают неуловимые загадки. YEA не делает различий между использованием перечислений в качестве индексов и идентификаторов, что приводит к путанице в коде, где все выглядит одинаково. …

Jack Giffin
22 февраля 2020 в 16:56
2

@Andrew ... YEA мешает компилятору Closure минимизировать. Сравните исходный код с YEA (3549 байтов) с минимизированным кодом с YEA (1344 байта) с минимизированным кодом с моим решением (604 байта). Наконец, YEA включает в себя «сопоставление по имени», потому что оно отделяет имена строк от пронумерованных идентификаторов. В моем случае учитывается только идентификатор, поэтому «сопоставление по имени» не требуется, что упрощает дизайн и повышает производительность. Спасибо, что поделились своим решением, но для того, чтобы оно стало практичным, необходимо много исправлений.

Andrew
24 февраля 2020 в 01:51
1

Расширение перечислений подклассами - определенно продвинутая функция ... Это можно реализовать самостоятельно. Перечисление, дающее undefined для несуществующей записи, является правильным поведением ... Если вы действительно используете число в качестве ключа перечисления, это на вас ... Я уже установил, что размер кода не вызывает особого беспокойства. Ваше решение буквально требует, чтобы люди вводили эти тупые ключи перечисления, такие как ENUM_QT_BLAHBLAH_BLAHBLAHBLAH, никто не хочет их использовать. Mine сохраняет код читаемым, что очень важно в реальном JS-программировании. Вы не разбираетесь в практичности.

Andrew
24 февраля 2020 в 01:52
1

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

Jack Giffin
24 февраля 2020 в 22:13
1

@ Андрей. Вы имеете право на свое мнение, как и я на свое 👍

java-addict301
13 октября 2020 в 18:53
1

чрезмерное форматирование сообщений и код в виде изображений. Рекомендую отредактировать.

avatar
Julius
1 мая 2018 в 11:32
4

Вот как 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
avatar
Govind Rai
15 марта 2018 в 21:09
30

Использовать 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:

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

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

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

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

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

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


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


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

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

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

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

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

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

sscarduzio
31 января 2019 в 17:51
0

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

Govind Rai
31 января 2019 в 18:00
0

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

sscarduzio
1 февраля 2019 в 13:33
0

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

Govind Rai
1 февраля 2019 в 22:26
1

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

avatar
Joseph Merdrignac
14 февраля 2018 в 23:44
5

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

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

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


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

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

for (let entry from key.enum)
     console.log(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(', ') }

}
avatar
David Lemon
26 июля 2017 в 11:13
0

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

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.log(musicStyles)
avatar
mika
12 апреля 2017 в 08:13
-1

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

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

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

avatar
Abdennour TOUMI
6 января 2017 в 07:07
17

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

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

затем

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

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

 class ColorEnum  extends Enum {/*....*/}
Jon G
1 февраля 2017 в 09:57
4

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

Bergi
9 июня 2017 в 01:43
7

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

Codii
6 июля 2017 в 13:30
3

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

xpto
6 октября 2017 в 07:31
0

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

Engineer
11 февраля 2020 в 16:53
1

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

avatar
Little Alien
2 ноября 2016 в 22:20
1

Решение 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.log(kwords, kwords.if, kwords.if == "if", kwords.undef)

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

avatar
Marcus Junius Brutus
30 августа 2016 в 08:16
4

Несмотря на то, что только статические методы (а не статические свойства) поддерживаются в 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.

Преимущество этого метода перед большинством других ответов заключается в том, что вы можете использовать его вместе со средством проверки статического типа (например, Flow), и вы можете утверждать во время разработки, используя статический тип проверка того, что ваши переменные, параметры и т. д. относятся к определенному «перечислению» 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
17 мая 2018 в 22:44
0

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

avatar
Ilya Gazman
17 августа 2016 в 12:38
5

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

Создать

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

Получить значение

console.log(Status.Ready) // 1

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

console.log(Object.keys(Status)[Status.Ready]) // Ready
avatar
LNT
25 мая 2016 в 06:32
4

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

    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

avatar
Muhammad Awais
11 мая 2016 в 10:31
2

Вы можете попробовать следующее:

   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');
    }
avatar
Oooogi
6 января 2016 в 15:20
4

Я создал класс 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>..."
avatar
Jules Sam. Randolph
25 октября 2015 в 04:03
3

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

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


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

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

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

user1228
14 июля 2016 в 17:23
0

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

Jules Sam. Randolph
14 июля 2016 в 18:07
0

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

user1228
14 июля 2016 в 18:13
1

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

Jack Giffin
23 июня 2018 в 23:27
0

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

avatar
Vivin Paliath
18 сентября 2015 в 18:08
7

Я придумал этот подход, который смоделирован на основе перечислений в 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
    }

})();
FBB
23 сентября 2015 в 11:25
0

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

avatar
Sherali Turdiyev
27 августа 2015 в 09:44
3

Думаю, им легко пользоваться. https://coderhelper.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"]));
avatar
hvdd
26 июля 2015 в 12:03
15

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

const Modes = {
  DRAGGING: 'drag',
  SCALING:  'scale',
  CLICKED:  'click'
};
rvighne
12 июля 2016 в 22:47
12

const не делает свойства объекта неизменяемыми, это только означает, что переменная Modes не может быть переназначена чему-то другому. Чтобы сделать его более полным, используйте Object.freeze() вместе с const.

Jack Giffin
7 октября 2018 в 13:21
0

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

avatar
Manohar Reddy Poreddy
2 июля 2015 в 13:03
6

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
}
avatar
Vitalii Fedorenko
5 мая 2015 в 16:32
57

В большинстве современных браузеров существует примитивный тип данных символ, который можно использовать для создания перечисления. Это обеспечит безопасность типа перечисления, поскольку 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
vbraun
16 июня 2015 в 08:14
0

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

rvighne
12 июля 2016 в 22:49
1

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

Andy
25 июля 2018 в 00:09
6

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

Andy
19 декабря 2018 в 02:10
6

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

Ciro Santilli 新疆再教育营六四事件法轮功郝海东
22 октября 2019 в 08:33
0

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

avatar
Pylinux
5 мая 2015 в 06:20
0
var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });

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

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

РЕДАКТИРОВАТЬ: Если вы собираетесь сериализовать объект (например, в JSON), вы снова получите id .

Little Alien
2 ноября 2016 в 21:30
1

{red:{green:{blue:{}}}}.unwindme()!

Stijn de Witt
1 февраля 2017 в 09:51
0

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

avatar
Gelin Luo
27 апреля 2015 в 11:13
4

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

var genEnum = require('gen_enum');

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

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

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

Обновления

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

Для обновления с gen_enum на constjs просто измените оператор

var genEnum = require('gen_enum');

до

var genEnum = require('constjs').enum;
avatar
Tschallacka
17 марта 2015 в 08:43
1

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

Перечисления 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"))});
avatar
Blake Bowen
16 марта 2015 в 10:42
4

Вот несколько разных способов реализации перечислений 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
avatar
Gildas.Tambo
15 февраля 2015 в 13:18
1

Вы можете использовать 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"))
avatar
Shivanshu Goyal
29 января 2015 в 01:32
5
var ColorEnum = {
    red: {},
    green: {},
    blue: {}
}

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

Domino
8 декабря 2015 в 21:16
0

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

Andrew
19 февраля 2020 в 20:22
0

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

avatar
Andrew Philips
5 сентября 2014 в 19:09
1

Мне действительно нравится то, что сделал @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.log("MED := " + Sizes.MEDIUM);
console.log("LRG := " + Sizes.LARGE);

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

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

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

    console.log("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, (использовать перечисления, присваивать значения, создавать и добавлять список свойств). Однако, поскольку я уже потратил на это слишком много времени сегодня, я оставлю комбинацию в качестве упражнения для уважаемого читателя.

avatar
Xeltor
15 мая 2014 в 04:20
8

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

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
Xeltor
14 декабря 2018 в 15:09
1

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

avatar
arcseldon
4 мая 2014 в 11:14
3

На момент написания, октябрь 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
2 января 2015 в 03:08
5

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

David
17 января 2015 в 19:55
5

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

avatar
user2254487
1 октября 2013 в 09:02
4

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

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

console.log(Colors.WHITE)  //this prints out "0"
Sildoreth
16 января 2014 в 19:19
6

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

avatar
GTF
26 сентября 2013 в 16:46
2

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

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

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

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

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

GTF
26 сентября 2013 в 17:03
0

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

avatar
Duncan
21 августа 2013 в 10:34
23

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

Используя 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
HBP
21 августа 2013 в 17:15
3

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

Duncan
21 августа 2013 в 17:20
0

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

Andrew Philips
5 сентября 2014 в 18:44
0

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

Duncan
8 сентября 2014 в 17:32
0

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

ceving
2 ноября 2015 в 16:03
1

false - значение по умолчанию для writable, enumerable и configurable. Не нужно пережевывать настройки по умолчанию.

avatar
Rob Hardy
24 июня 2013 в 16:11
17

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

David Karlaš
5 января 2014 в 09:41
1

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

parliament
28 июля 2015 в 23:42
3

и, конечно, если вы действительно используете Typescript: enum MyEnum { Foo, Bar, Foobar }

avatar
David Miró
11 апреля 2013 в 12:11
7

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

  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
avatar
Chris
15 мая 2012 в 09:26
15

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

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

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

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

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

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

  • Здесь происходит какое-то беспорядочное управление памятью, поскольку я сохраняю ссылки на перечисления
  • Все еще нет безопасности типа
avatar
Yaroslav
24 апреля 2012 в 14:04
11

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

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

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

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

// using
Colors.Red.print()
avatar
Andre 'Fi'
13 июля 2011 в 00:28
59

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

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), и они получают последовательное целое значение (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
19 июня 2019 в 22:00
0

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

Andrew
19 февраля 2020 в 21:26
0

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

Jack Giffin
22 июля 2020 в 23:43
0

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

avatar
Stijn de Witt
4 марта 2010 в 22:31
516

ОБНОВЛЕНИЕ

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

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

Stijn de Witt
29 ноября 2011 в 10:43
2

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

goodeye
29 апреля 2014 в 15:35
2

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

Andrew Philips
5 сентября 2014 в 19:10
0

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

Roman M
27 сентября 2014 в 15:35
0

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

Kevin Wiskia
9 августа 2016 в 14:55
0

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

Stijn de Witt
10 августа 2016 в 21:31
0

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

avatar
Randolpho
21 августа 2009 в 20:56
89

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

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

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

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

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

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

Изменить

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

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

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

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

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

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

Scott Evernden
21 августа 2009 в 21:02
106

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

kangax
22 августа 2009 в 03:20
3

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

Randolpho
22 августа 2009 в 20:14
3

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

Stijn de Witt
29 ноября 2011 в 11:04
2

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

Michael
5 января 2012 в 15:58
3

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

avatar
Gareth
13 ноября 2008 в 19:13
623

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

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

Sildoreth
16 января 2014 в 19:29
387

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

Ky Leggiero
9 апреля 2014 в 05:26
5

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

Tolga E
9 апреля 2014 в 15:00
3

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

Vivin Paliath
18 сентября 2015 в 22:16
2

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

Rabadash8820
6 июля 2017 в 18:51
3

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

Govind Rai
11 декабря 2018 в 23:12
0

Обновление ES6: используйте прокси-серверы JavaScript для имитации перечислений. Выдавать ошибки времени компиляции при попытке доступа к несуществующим перечислителям - как и в традиционных перечислениях. 10 строк кода. coderhelper.com/a/49309248/2757916

Jack Giffin
10 июля 2019 в 18:08
1

@GovindRai Пожалуйста, 𝗻𝗼𝘁 𝘂𝘀𝗲 𝗽𝗿𝗼𝘅𝗶𝗲𝘀: прокси выдают исключение только при доступе к ним (есть 𝗻𝗼 "𝗰𝗼𝗺𝗽𝗶𝗹𝗲-𝘁𝗶𝗺𝗲" 𝗲𝗿𝗿𝗼𝗿𝘀), ужасно 𝗶𝗹𝗹-𝗽𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝘁 и строго 𝗯𝗹𝗼𝗮𝘁 𝗺𝗶𝗻𝗶𝗳𝗶𝗲𝗱 𝘀𝗶𝘇𝗲. Решение, которое решает все эти проблемы, см. На coderhelper.com/a/50355530/5601591.