Pytanie Jaka jest preferowana składnia do definiowania wyliczeń w JavaScript?


Jaka jest preferowana składnia do definiowania wyliczeń w JavaScript? Coś jak:

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

// later on

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

Czy istnieje bardziej preferowany idiom?


1678
2017-11-13 19:09


pochodzenie


Brałem test na JavaScript 1.8 na odesk i pytali, co ma rozwiązać wyliczenia w javascript, 2 oczywiste odpowiedzi były albo słowem kluczowym let lub generatorami. Niejasno pamiętam wyliczenia w moim kursie c ++ i naprawdę nie wiem, czym są generatory; ale potencjalnie to pomaga. - Devin G Rhode
Nie używaj 0 jako numer wyliczeniowy. Chyba że jest używany do czegoś, co nie zostało ustawione. JS traktuje false || undefined || null || 0 || "" || '' || NaN wszystkie jako taka sama wartość w porównaniu z użyciem ==. - matsko
@matsko nie jest tylko argumentem przeciwko używaniu ==? - sdm350
Ale false == 0 i +null == 0 (i konwersje na numery zdarzają się czasami, gdy się tego nie spodziewasz), podczas gdy null == undefined też i +undefined jest NaN (chociaż NaN != NaN). - sanderd17
Podwójna macierz równości jest bardziej myląca niż automatyczne formatowanie słowa microsoft - aaaaaa


Odpowiedzi:


Od 1.8.5 możliwe jest uszczelnienie i zamrożenie obiektu, dlatego zdefiniuj powyższe jako:

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

lub

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

i voila! JS wylicza.

Nie uniemożliwia to jednak przypisania niepożądanej wartości do zmiennej, która często jest głównym celem wyliczenia:

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

Jednym ze sposobów zapewnienia większego stopnia bezpieczeństwa typu (z wyliczeniami lub innymi) jest użycie narzędzia takiego jak Maszynopis lub Pływ.

Źródło

Cytaty nie są potrzebne, ale zachowałem je dla spójności.


541
2018-02-18 11:03



Według Wikipedii (en.wikipedia.org/wiki/JavaScript#Versions) ma zastosowanie do Firefoksa 4, IE 9, Opery 11.60 i wiem, że działa w Chrome. - Artur Czajka
To właściwa odpowiedź już w 2012 roku. Bardziej proste: var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });. Nie musisz podawać identyfikatora, możesz po prostu użyć pustego obiektu do porównania wyliczeń. if (incommingEnum === DaysEnum.monday) //incommingEnum is monday - Gabriel Llamas
Ze względu na kompatybilność wsteczną, if (Object.freeze) { Object.freeze(DaysEnum); } - saluce
Chciałbym zwrócić uwagę na to ({ monday: {},  itp. oznacza, że ​​jeśli skonwertujesz ten obiekt na JSON poprzez stringify, otrzymasz [{"day": {}}] co nie zadziała. - jcollum
@Supuhstar Moja opinia na temat tego pytania jest teraz inna. Nie używaj funkcji freeze (), jest całkowicie bezużyteczna, a strata czasu robi "głupie" rzeczy. Jeśli chcesz wyeksponować wyliczenie, po prostu wyeksponuj to: var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}. Porównywanie obiektów, takich jak w moim poprzednim komentarzu, jest DUŻO WIĘKSZYM NISKIMI niż porównywanie liczb. - Gabriel Llamas


To nie jest duża odpowiedź, ale powiedziałbym, że działa dobrze, osobiście

Powiedziawszy to, ponieważ nie ma znaczenia, jakie są wartości (użyłeś 0, 1, 2), użyłbym znaczącego ciągu na wypadek, gdybyś chciał wypisać aktualną wartość.


583
2017-11-13 19:13



Oto prosta fabryka enum, którą stworzyłem; npmjs.org/package/simple-enum - Tolga E
Przepraszam, ale ta implementacja enum ma kilka problemów. Zalecam używanie tego zamiast: github.com/rauschma/enums - paldepind
Zostało to stwierdzone w innej odpowiedzi, ale ponieważ ta odpowiedź jest akceptowaną odpowiedzią, zamieszczę ją tutaj. Rozwiązanie OP jest poprawne. Będzie jednak jeszcze lepiej, jeśli użyje się go Object.freeze(). Zapobiegnie to zmianie wartości enum przez inny kod. Przykład: var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2}); - Sildoreth
@TolgaE dziękuję za tę bibliotekę! Zainspirowało mnie to nie tylko do sprowadzenia go do minimum, ale także do dodania kilku funkcji! Rozwinąłem twoje i umieściłem to wszystko tutaj: github.com/BlueHuskyStudios/Micro-JS-Enum - Supuhstar
@Supuhstar To świetnie! Cieszę się, że mógłbyś z niego skorzystać. Jeśli chcesz, aby została scalona w tej bibliotece, możesz wysłać żądanie ściągnięcia, a następnie mogę zaktualizować bibliotekę npm - Tolga E


AKTUALIZACJA: Dziękuję za wszystkie upvotes wszystkim, ale nie sądzę, że moja odpowiedź poniżej jest najlepszym sposobem, aby napisać wyliczenia w JavaScript już. Zobacz mój post na blogu, aby uzyskać więcej informacji: Wylicza w JavaScript.


Alarmowanie nazwy jest już możliwe:

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

Alternatywnie możesz utworzyć wartości obiektów, abyś mógł zjeść ciastko i je zjeść:

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

W JavaScript, ponieważ jest to język dynamiczny, możliwe jest nawet dodanie wartości wyliczeniowych do zestawu później:

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

Pamiętaj, że pola enum (wartość, nazwa i kod w tym przykładzie) nie są potrzebne do sprawdzenia tożsamości i są dostępne tylko dla wygody. Również nazwa samej właściwości rozmiaru nie musi być sztywno zakodowana, ale może również być ustawiana dynamicznie. Załóżmy, że znasz tylko nazwę nowej wartości wyliczeniowej, ale możesz ją dodać bez problemów:

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

Oczywiście oznacza to, że niektóre założenia nie mogą być już wykonane (ta wartość reprezentuje poprawną kolejność na przykład).

Pamiętaj, że w JavaScript obiekt jest jak mapa lub hashtable. Zestaw par nazwa-wartość. Możesz je przeglądać lub w inny sposób nimi manipulować bez wcześniejszej wiedzy na ich temat.

NA PRZYKŁAD:

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

A przy okazji, jeśli interesują Cię przestrzenie nazw, możesz chcieć rzucić okiem na moje rozwiązanie dla prostego, ale potężnego obszaru nazw i zarządzania zależnościami dla javascript: Pakiety JS


477
2018-03-04 22:31



więc jak byś poszła i stworzyła SIZE, gdybyś miał tylko swoją nazwę? - Johanisma
@Johanisma: Ten przypadek użycia nie ma sensu dla wyrażeń, ponieważ cała ich idea polega na tym, że znasz wszystkie wartości z góry. Jednak nic nie powstrzyma cię przed dodaniem dodatkowych wartości później w JavaScript. Dodam przykład tego do mojej odpowiedzi. - Stijn de Witt
Bardzo dobrze odpowiedział. W razie potrzeby jestem zwykłym użytkownikiem JS / JQ, co pomogło w sytuacji, w której program SharePoint wymagał rozwiązania. - GoldBishop
+1 dla linku do Twojego wpisu z podejściem właściwości. Elegancki w tym, że podstawowe deklaracje są proste, tak jak w OP, z dodatkowymi cechami właściwości w razie potrzeby. - goodeye
@DavideAndrea Masz rację. Odtąd zacząłem hostować mój blog pod moją własną nazwą domeny. Zaktualizowano link. Dzięki za wzmiankę o tym! - Stijn de Witt


Konkluzja: Nie możesz.

Możesz go sfałszować, ale nie uzyskasz bezpieczeństwa typu. Zazwyczaj odbywa się to poprzez utworzenie prostego słownika wartości łańcuchowych odwzorowanych na wartości całkowite. Na przykład:

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

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

Problem z tym podejściem? Możesz przypadkowo przedefiniować swój enumerant lub przypadkowo mieć zdublowane wartości liczbowe. Na przykład:

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

Edytować 

A co z Object.freeze Artura Czajki? Czy to nie powstrzymałoby cię od poniedziałku do czwartku? - Fry Quad

Absolutnie, Object.freeze całkowicie rozwiązałbym problem, na który się skarżyłem. Chciałbym przypomnieć wszystkim, że kiedy napisałem powyższe, Object.freeze tak naprawdę nie istniało.

Teraz .... teraz otwiera trochę bardzo interesujące możliwości.

Edytuj 2
Oto bardzo dobra biblioteka do tworzenia wyrażeń.

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

Choć prawdopodobnie nie pasuje do każdego prawidłowego użycia wyrażeń, to idzie bardzo długą drogą.


79
2017-08-21 20:56



w javascript jest bezpieczeństwo typów? - Scott Evernden
Więc nie mapuj wartości do właściwości obiektu. Użyj gettera, aby uzyskać dostęp do enumeranta (przechowywanego jako własność, powiedzmy, "prywatnego" obiektu). Wyglądałoby naiwne wdrożenie - var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1 - kangax
@Scott Evernden: punkt wzięty. @kangax: chodzi o to, że to wciąż hack. Enums po prostu nie istnieją w JavaScript, okresie, końcu historii. Nawet wzór sugerowany przez Tima Sylvestera jest wciąż mniej niż idealną sztuczką. - Randolpho
Zraszanie kodu literałami nie jest zbyt łatwe w utrzymaniu, więc ma sens tworzenie dla niego stałych. Oczywiście JavaScript nie ma stałych. Zasadniczo jest to po prostu sposób na napisanie czystego kodu. Nie można go wymusić, ale nie za dużo w JavaScript. Możesz ponownie zdefiniować stałe lub funkcje, lub w większości cokolwiek. EG: document.getElementById = function () {alert ("Jesteś skręcony, JavaScript nie jest bezpieczny");}; - Stijn de Witt
@Randolpho: A co z Object.freeze Artura Czajki? Czy to nie powstrzymałoby cię od poniedziałku do czwartku? - Michael


Oto czego wszyscy chcemy:

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

Teraz możesz utworzyć swoje wyliczenia:

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

W ten sposób można uzyskać stałe w zwykły sposób (YesNo.YES, Color.GREEN) i uzyskać sekwencyjną wartość int (NO = 0, YES = 1, RED = 0, GREEN = 1, BLUE = 2).

Możesz także dodać metody, używając 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 */
};


Edycja - mała poprawa - teraz z varargs: (niestety nie działa poprawnie na IE: S ... powinna pozostać przy poprzedniej wersji)

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

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

46
2017-07-13 00:28





W większości nowoczesnych przeglądarek istnieje symbol prymitywny typ danych, który może być użyty do utworzenia wyliczenia. Zapewni to bezpieczeństwo typu enum, ponieważ każda wartość symbolu jest gwarantowana przez JavaScript, aby była unikalna, tj. Symbol() != Symbol(). Na przykład:

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

Aby uprościć debugowanie, możesz dodać opis do wyliczania wartości:

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

Demo Plunkera

Na GitHub możesz znaleźć opakowanie, które upraszcza kod wymagany do zainicjowania wyliczenia:

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

41
2018-05-05 16:32



To jest poprawna odpowiedź w teorii. W praktyce obsługa przeglądarki w 2015 roku jest dalece niewystarczająca. Nie jest jeszcze gotowy do produkcji. - vbraun
Chociaż obsługa przeglądarki nie jest jeszcze dostępna, jest to najlepsza odpowiedź, ponieważ jest blisko Symbol Jest przeznaczony do. - rvighne
Meh ... wartości wyliczeniowe często muszą być możliwe do serializacji, a symbole nie są tak przydatne do serializowania i deserializacji. - Andy


Bawiłem się z tym, bo uwielbiam moje wyliczenia. =)

Za pomocą Object.defineProperty Myślę, że wymyśliłem nieco realistyczne rozwiązanie.

Oto jsfiddle: http://jsfiddle.net/ZV4A6/

Korzystając z tej metody, powinieneś (teoretycznie) móc wywołać i zdefiniować wartości wyliczeniowe dla dowolnego obiektu, bez wpływu na inne atrybuty tego obiektu.

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

Ze względu na atrybut writable:false to powinien spraw, aby był bezpieczny.

Więc powinieneś być w stanie stworzyć niestandardowy obiekt, a następnie zadzwoń Enum() na tym. Przypisane wartości zaczynają się od 0 i zwiększają się o jedną pozycję.

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

23
2017-08-21 10:34



Jeśli dodasz return this; na końcu Enum możesz zrobić: var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW'); - HBP
Nie uważałem tego, ponieważ nie jest to moja normalna metoda robienia rzeczy. Ale masz absolutną rację! Będę edytować to w. - Duncan
Bardzo mi się to podoba, chociaż nie jestem wielkim fanem muckowania przestrzeni Object (z globalną funkcją ENUM). Przekonwertowano to na funkcję mkenum i dodano opcjonalne przypisania numeryczne => var mixedUp = mkenum ("CZARNY", {CZERWONY: 0x0F00, NIEBIESKI: 0X0F, ZIELONY: 0x0F0, BIAŁY: 0x0FFF, JEDEN: 1}, DWIE, TRZECIE, CZTERY) ; // Dodanie mojego kodu jako odpowiedzi poniżej. Dzięki. - Andrew Philips
Szczerze mówiąc, już tego nie używam. Korzystam z Kompilatora zamykania Google, a to nie działa zbyt dobrze (lub po prostu komplikuje), jeśli używasz ustawienia Zaawansowane. Więc właśnie wróciłem do standardowej notacji obiektów. - Duncan
false jest ustawieniem domyślnym dla writable, enumerable i configurable. Nie trzeba żuć ponad domyślne wartości. - ceving


Jest to stary, jaki znam, ale sposób, w jaki został zaimplementowany za pośrednictwem interfejsu TypeScript:

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

To pozwala spojrzeć na obie MyEnum.Bar która zwraca 1, i MyEnum[1] która zwraca "Bar" bez względu na kolejność deklaracji.


17
2018-06-24 16:11



Plus MyEnum ["Bar"] działa, który zwraca 1 ... <3 TypeScript do tej pory ... - David Karlaš
i oczywiście jeśli faktycznie używasz maszynopisu: enum MyEnum { Foo, Bar, Foobar } - parliament


To jest rozwiązanie, którego używam.

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

I definiujesz swoje wyliczenia w ten sposób:

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

I w ten sposób uzyskujesz dostęp do swoich wyliczeń:

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

Zwykle używam dwóch ostatnich metod odwzorowywania enum od obiektów wiadomości.

Niektóre zalety tego podejścia:

  • Łatwe deklarowanie wyliczeń
  • Łatwy dostęp do twoich wyliczeń
  • Twoje wyliczenia mogą być złożonymi typami
  • Klasa Enum ma pewne asocjacyjne buforowanie, jeśli często korzystasz z getByValue

Niektóre wady:

  • Trochę nieporządnego zarządzania pamięcią, bo zachowuję odniesienia do wyrażeń
  • Nadal nie ma żadnego rodzaju bezpieczeństwa

15
2018-05-15 09:26





Jeśli używasz Kręgosłup, możesz uzyskać pełną funkcjonalność enum (find by id, name, custom members) do darmowego użycia 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()

12
2018-04-24 14:04