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

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

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

// later on

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

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

30 ответов


С 1.8.5 можно запечатать и заморозить объект, чтобы определить, как:

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

или

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

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

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

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

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

источник

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


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

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


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

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

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

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

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

Например:

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

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


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

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

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

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

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

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

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

как насчет Артура Чайки Объект.замерзнуть? Разве это не помешает вам с понедельника по четверг? - Фрай Квад!--5-->

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

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

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

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

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


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

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.Да, цвет.Зеленый), и они получают последовательное значение int (NO = 0, YES = 1; RED = 0, GREEN = 1, BLUE = 2).

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

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


Edit-небольшое улучшение-теперь с 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');

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

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

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

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

Plunker demo

On 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

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

используя 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

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


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

function Enum() {
    this._enums = [];
    this._lookups = {};
}

Enum.prototype.getEnums = function() {
    return _enums;
}

Enum.prototype.forEach = function(callback){
    var length = this._enums.length;
    for (var i = 0; i < length; ++i){
        callback(this._enums[i]);
    }
}

Enum.prototype.addEnum = function(e) {
    this._enums.push(e);
}

Enum.prototype.getByName = function(name) {
    return this[name];
}

Enum.prototype.getByValue = function(field, value) {
    var lookup = this._lookups[field];
    if(lookup) {
        return lookup[value];
    } else {
        this._lookups[field] = ( lookup = {});
        var k = this._enums.length - 1;
        for(; k >= 0; --k) {
            var m = this._enums[k];
            var j = m[field];
            lookup[j] = m;
            if(j == value) {
                return m;
            }
        }
    }
    return null;
}

function defineEnum(definition) {
    var k;
    var e = new Enum();
    for(k in definition) {
        var j = definition[k];
        e[k] = j;
        e.addEnum(j)
    }
    return e;
}

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

var COLORS = defineEnum({
    RED : {
        value : 1,
        string : 'red'
    },
    GREEN : {
        value : 2,
        string : 'green'
    },
    BLUE : {
        value : 3,
        string : 'blue'
    }
});

и вот как вы получаете доступ к перечислениям:

COLORS.BLUE.string
COLORS.BLUE.value
COLORS.getByName('BLUE').string
COLORS.getByValue('value', 1).string

COLORS.forEach(function(e){
    // do what you want with e
});

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

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

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

недостатки:

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

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

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

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

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

затем

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

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

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

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

const Modes = {
  DRAGGING: 'drag',
  SCALING:  'scale',
  CLICKED:  'click'
};

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

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

// later on

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

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

(длина: 2,450 байт)

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

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

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

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

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

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

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

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

Источник Без Перечисления (длина: 1,973 байт (короче!))
Minified Без Перечислений (длина: 843 байт (больше))

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


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

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

Я изменил решение Andre 'Fi':

  function Enum() {
    var that = this;
    for (var i in arguments) {
        that[arguments[i]] = i;
    }
    this.name = function(value) {
        for (var key in that) {
            if (that[key] == value) {
                return key;
            }
        }
    };
    this.exist = function(value) {
        return (typeof that.name(value) !== "undefined");
    };
    if (Object.freeze) {
        Object.freeze(that);
    }
  }

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

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

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

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

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

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

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

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

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

// Generic Factory Function for creating enums (10 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

function createEnum(enumObj) {

  // 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. This can be done by defining the "get" trap
  
  const enumHandler = {
    get: function(target, name) {
      if (target[name]) {
        return target[name]
      }
      throw new Error(`No such enumerator: ${name}`)
    }
  }
  
  
  // Freeze the target object to prevent modifications
  return new Proxy(Object.freeze(enumObj), enumHandler)
}


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

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

httpMethods.delete = "delete" 
// no effect due to Object.freeze, fails silently (no error thrown)

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

в сторону: Что такое прокси-сервер?

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


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

в моем текущем игровом проекте я использовал ниже, так как немногие клиенты все еще используют 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, etc. должен быть уникальным.) Обратите внимание, что вы должны поддерживать их навсегда Для обратной совместимости.

назначение:

var wildType = CONST_WILD_TYPES.REGULAR;

сравнение:

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

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

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

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

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

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

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

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

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

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

вы можете init'D это так:

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

получить значение (например, перечисления в C#):

var val2 = enum1.item2;

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

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

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

var arr = enum1.GetObjArr();

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

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

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

var html = enum1.GetSelectOptionsHTML();

который имеет:

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

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

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

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

вот несколько различных способов реализации перечисления машинописного текста.

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

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

Я только что опубликовал пакет NPM gen_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+), возвращаемый объект перечисления неизменяем.

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

обновления

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

обновить gen_enum to constjs просто измените инструкцию

var genEnum = require('gen_enum');

to

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

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

})();

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

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

обновление

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

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

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

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

: перечисления.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');
        });
    });

});

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

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


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

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


вы можете сделать что-то подобное

function Enum(){
  this.add.apply(this,arguments);
}

Enum.prototype.add = function(){
  for (var i in arguments) {
    this[arguments[i]] = new String(arguments[i]);
  }
};
Enum.prototype.toList = function(){
  return Object.keys(this)
};

var STATUS = new Enum("CLOSED","PENDING");


var STATE = new Enum("CLOSED","PENDING");

STATE.CLOSED === STATUS.CLOSED  // false;
STATE.CLOSED === "CLOSED"  // false;
STATE.CLOSED.toString() === "CLOSED"  // true;

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


простое решение:

создать

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

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

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

здесь производящая функция перечисления, я сделал: https://gist.github.com/gfarrell/6716853

вы бы использовали его так:

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

и он создаст неизменяемую строку: int dictionary (перечисление).


Это простой в использовании, я думаю. https://stackoverflow.com/a/32245370/4365315

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

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

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

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

enumA.setType("a", true);

enumA.setTypeByIndex(1);

обновление:

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

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

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

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

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

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

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

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

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

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

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

    this.setDefaultType();
}

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

var enumA = new TypeHelper(TypeA, 4);

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


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

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


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

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