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


2082

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 jest bardziej preferowany idiom?


131
Nie należy używać 0jako numeru wyliczenia. Chyba że jest używany do czegoś, co nie zostało ustawione. JS traktuje false || undefined || null || 0 || "" || '' || NaNwszystko jako tę samą wartość w porównaniu z użyciem ==.
matsko

152
@matsko to nie tylko argument przeciwko używaniu ==?
sdm350

6
0 == nullzwraca false
mcont

11
Ale false == 0i +null == 0(a konwersje na liczby zdarzają się czasem, gdy się tego nie spodziewasz), podczas gdy null == undefinedteż i +undefinedjest NaN(chociaż NaN != NaN).
sanderd17

46
Macierz podwójnej równości jest bardziej myląca niż automatyczne formatowanie słowa Microsoft
aaaaaa

Odpowiedzi:


895

Od 1.8.5 istnieje możliwość uszczelnienia i zamrożenia obiektu, więc zdefiniuj powyższe jako:

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

lub

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

i voila! JS wylicza.

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

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 w inny sposób) jest użycie narzędzia takiego jak TypeScript lub Flow .

Źródło

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


6
Według Wikipedii ( en.wikipedia.org/wiki/JavaScript#Versions ) dotyczy przeglądarki Firefox 4, IE 9, Opera 11.60 i wiem, że działa w Chrome.
Artur Czajka

77
To jest właściwa odpowiedź już w 2012 roku bardziej prosty: 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,

34
Aby uzyskać zgodność z poprzednimi wersjami,if (Object.freeze) { Object.freeze(DaysEnum); }
saluce

17
Chciałbym ({ monday: {}, zauważyć , że robienie itp. Oznacza, że ​​jeśli przekształcisz ten obiekt w JSON za pomocą stringify, otrzymasz to, [{"day": {}}]co nie zadziała.
jcollum

10
@Supuhstar Moja opinia na temat tego pytania jest teraz inna. Nie używaj freeze (), jest to całkowicie bezużyteczne i marnowanie czasu na robienie „głupich” rzeczy. Jeśli chcesz narazić enum, wystarczy odsłonić to: var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}. Porównywanie obiektów jak w moim poprzednim komentarzu jest DUŻO WOLNIEJSZE niż porównywanie liczb.
Gabriel Llamas

608

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

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ł kiedyś wyświetlić bieżącą wartość.


377
Stwierdzono to w innej odpowiedzi, ale ponieważ ta odpowiedź jest odpowiedzią zaakceptowaną, opublikuję ją tutaj. Rozwiązanie OP jest prawidłowe. Będzie jednak jeszcze lepiej, jeśli zostanie użyty z Object.freeze(). Zapobiegnie to zmianie przez inny kod wartości wyliczenia. Przykład:var ColorEnum = Object.freeze({RED: 0, GREEN: 1, BLUE: 2});
Sildoreth,

5
@TolgaE dziękuję za tę bibliotekę! Zainspirowało mnie to nie tylko sprowadzenie go do absolutnego minimum, ale także dodanie kilku funkcji! Rozwidliłem
Supuhstar

3
@Supuhstar To świetnie! Cieszę się, że możesz go użyć. Jeśli chcesz, aby został scalony w tej bibliotece, możesz wysłać prośbę ściągnięcia, a następnie mogę zaktualizować bibliotekę npm
Tolga E

2
Jeśli ktoś jest zainteresowany, mam realizowane typu bezpieczny stałe teksty podobne do tego, jak są one w Javie. Oznacza to, że możesz przeprowadzać instanceofkontrole. Na przykład ColorEnum.RED instanceof ColorEnum(zwraca true). Możesz także rozwiązać instancję z nazwy ColorEnum.fromName("RED") === ColorEnum.RED(zwraca true). Każdy przypadek jest również .name()i w .ordinal()sposób, a sam wyliczenia ma values()metodę returnd tablicę wszystkich stałych.
Vivin Paliath,

3
Nie jestem pewien, czy zgadzam się z sugestią „znaczącego ciągu”. Wyliczeń nie należy traktować jako ciągów znaków lub liczb; są to abstrakcyjne typy danych. Nie powinno być możliwe „wyprowadzenie bieżącej wartości” bez jakiejś metody pomocniczej. W Javie i .NET jest to ToString()metoda. My, twórcy JS, jesteśmy już zbyt zależni od rzeczy „po prostu działających”! Ponadto, powinno być możliwe szybkie switchwyliczenie. Porównywanie ciągów jest wolniejsze niż porównywanie liczb, więc osiągniesz nieco gorszą switchwydajność, jeśli użyjesz ciągów zamiast liczb całkowitych.
Rabadash8820

501

AKTUALIZACJA

Dziękuję za wszystkie głosy poparcia dla wszystkich, ale nie sądzę, że moja odpowiedź poniżej jest najlepszym sposobem na pisanie wyliczeń w JavaScript. Zobacz mój post na blogu, aby uzyskać więcej informacji: Wyliczenia 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ść ciasto i zjeść je:

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 enum do zestawu później:

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

Pamiętaj, że pola wyliczenia (wartość, nazwa i kod w tym przykładzie) nie są potrzebne do sprawdzania tożsamości i są tam tylko dla wygody. Również nazwa właściwości size nie musi być zakodowana na stałe, ale może być również ustawiana dynamicznie. Załóżmy więc, że znasz tylko nazwę swojej nowej wartości wyliczeniowej, nadal 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 nie można już przyjąć pewnych założeń (ta wartość reprezentuje na przykład prawidłową kolejność dla rozmiaru).

Pamiętaj, że w JavaScript obiekt jest jak mapa lub tablica skrótów . Zestaw par nazwa-wartość. Możesz je przeglądać lub w inny sposób nimi manipulować, nie wiedząc o nich wiele wcześniej.

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 tak przy okazji, jeśli interesujesz się przestrzeniami nazw, możesz rzucić okiem na moje rozwiązanie dla prostego, ale potężnego zarządzania przestrzenią nazw i zależnościami dla JavaScript: Pakiety JS


więc jak byś poszedł i stworzył po prostu ROZMIAR, jeśli masz tylko jego nazwę?
Johanisma 10.11.11

2
@Johanisma: Ten przypadek użycia nie ma sensu dla wyliczeń, ponieważ cała ich idea polega na tym, że znasz wszystkie wartości z góry. Jednak nic nie stoi na przeszkodzie, aby dodać dodatkowe wartości później w Javascript. Dodam przykład tego do mojej odpowiedzi.
Stijn de Witt

2
+1 za link do twojego postu z podejściem właściwości. Eleganckie w tym, że podstawowe deklaracje są proste, podobnie jak w OP, z dodanymi właściwościami w razie potrzeby.
goodeye

@Stijin, naprawdę podobało Ci się twoje zaktualizowane rozwiązanie. Wysłany kod w komentarzach na blogu i jako komentarz poniżej. Zasadniczo, korzystając z funkcji, wykonaj kompilację właściwości z istniejącej listy skrótów i opcjonalnie zamroź ją (mkenum_2 na mojej liście). Twoje zdrowie.
Andrew Philips,

Istnieje również biblioteka, która ją implementuje, w tym także ładne funkcje porównania i wyszukiwania odwróconego: github.com/adrai/enum
Roman M

83

Konkluzja: Nie możesz.

Możesz to sfałszować, ale nie uzyskasz bezpieczeństwa typu. Zazwyczaj odbywa się to poprzez utworzenie prostego słownika wartości ciągów 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ć numerator lub przypadkowo mieć zduplikowane wartości modułu. Na przykład:

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

Edytować

A co z Object.freeze Artura Czajki? Czy nie zadziałałoby to, aby uniemożliwić ustawienie od poniedziałku do czwartku? - Fry Quad

Absolutnie Object.freezecałkowicie rozwiązałoby problem, na który narzekałem. Chciałbym przypomnieć wszystkim, że kiedy napisałem powyższe, Object.freezetak naprawdę nie istniały.

Teraz .... teraz otwiera kilka bardzo interesujących możliwości.

Edycja 2
Oto bardzo dobra biblioteka do tworzenia wyliczeń.

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

Chociaż prawdopodobnie nie pasuje do każdego prawidłowego użycia wyliczeń, idzie bardzo daleko.


103
w javascript jest bezpieczeństwo typu?
Scott Evernden,

3
Więc nie mapuj wartości na właściwości obiektu. Użyj gettera, aby uzyskać dostęp do wyliczenia (przechowywanego jako właściwość, powiedzmy, „prywatnego” obiektu). Naiwna implementacja wyglądałaby jak -var daysEnum = (function(){ var daysEnum = { monday: 1, tuesday: 2 }; return { get: function(value){ return daysEnum[value]; } } })(); daysEnum.get('monday'); // 1
kangax

2
@Scott Evernden: punkt zajęty. @kangax: chodzi o to, że to wciąż hack. Wyliczenia po prostu nie istnieją w Javascript, kropka, koniec historii. Nawet wzorzec sugerowany przez Tima Sylvester'a to wciąż niezbyt idealny hack.
Randolpho,

2
Posypanie kodu literałami nie jest łatwe do utrzymania, więc warto dla niego stworzyć stałe. Oczywiście Javascript również nie ma stałych. Zasadniczo jest to tylko sposób na napisanie czystego kodu. Nie można tego egzekwować, ale niewiele można w Javascript. Możesz ponownie zdefiniować stałe, funkcje lub przeważnie cokolwiek. EG: document.getElementById = function () {alert („Jesteś wkręcony. JavaScript nie jest bezpieczny dla typów.”);};
Stijn de Witt

3
@Randolpho: A co z Object.freeze Artura Czajki? Czy nie zadziałałoby to, aby uniemożliwić ustawienie od poniedziałku do czwartku?
Michael - Where's Clay Shirky

56

Oto, czego wszyscy chcemy:

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

Teraz możesz tworzyć swoje wyliczenia:

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

W ten sposób można uzyskać dostęp do stałych w zwykły sposób (Tak Nie Nie TAK, Kolor ZIELONY) i otrzymują one sekwencyjną wartość int (NIE = 0, TAK = 1; CZERWONY = 0, ZIELONY = 1, NIEBIESKI = 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 poprawka - 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');

Uwielbiam prostotę tej odpowiedzi!
Marquizzo,

@Marquizzo (i OP) Stworzyłem ulepszoną wersję na podstawie tej odpowiedzi: stackoverflow.com/a/60309416/1599699
Andrew

53

W większości nowoczesnych przeglądarek, jest symbolem prymitywny typ danych, które mogą być wykorzystywane do tworzenia wyliczenie. Zapewni to bezpieczeństwo typu wyliczenia, ponieważ każda wartość symbolu jest gwarantowana przez JavaScript jako unikalny, tj Symbol() != Symbol(). Na przykład:

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

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

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

Demo gry Plunker

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

To jest poprawna odpowiedź w teorii. W praktyce obsługa przeglądarki w 2015 r. Nie jest wystarczająca. Zdecydowanie brak produkcji.
vbraun

1
Chociaż obsługa przeglądarek jeszcze nie istnieje, jest to najlepsza odpowiedź, ponieważ jest ona zbliżona do tego, do czego Symboljest przeznaczona.
rvighne

2
Meh ... wartości wyliczeniowe często muszą być możliwe do serializacji, a Symbole nie są tak przydatne do serializacji i deserializacji.
Andy,

3
Czy to tylko ja, czy Object.freezetylko dla osób, które nie zaakceptowały faktu, że „małpka na własne ryzyko” jest umową społeczną JS?
Andy,

@Andy tak serializacja jest denerwująca. Skończyło się na jawnym toJSONokreśleniu klasy zawierającej, aby zastosować to podejście: stackoverflow.com/questions/58499828/...
Ciro Santilli 22 病毒 审查 六四 事件 法轮功

30

𝗣𝗹𝗮𝗶𝗻 𝗩𝗮𝗻𝗶𝗹𝗹𝗮𝗝𝗦 𝗩𝗮𝗿𝗶𝗮𝗯𝗹𝗲 𝗡𝗮𝗺𝗲𝘀

Przejdźmy od razu do problemu: rozmiaru pliku. Każda inna odpowiedź wymieniona tutaj powoduje, że Twój kod jest skrajnie rozdęty. Przedstawiam wam, że dla najlepszej możliwej wydajności, czytelności kodu, zarządzania projektami na dużą skalę, podpowiedzi składniowych w wielu edytorach kodu i zmniejszenia rozmiaru kodu przez zmniejszenie, jest to właściwy sposób wykonywania wyliczeń: zmienne podkreślenia.


wvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvwvw

Zmienne podkreślenia-notacji

Jak pokazano w powyższej tabeli i poniższym przykładzie, oto pięć łatwych kroków do rozpoczęcia:

  1. Określ nazwę grupy wyliczeń. Pomyśl o rzeczowniku, który może opisać cel wyliczenia lub przynajmniej wpisy w wyliczeniu. Na przykład grupa wyliczeń reprezentujących kolory wybrane przez użytkownika może być lepiej nazwana COLORCHOICES niż COLORS.
  2. Zdecyduj, czy wyliczenia w grupie wzajemnie się wykluczają, czy są niezależne. Jeśli wykluczają się wzajemnie, rozpocznij każdą wyliczoną nazwę zmiennej od ENUM_. Jeśli jest to niezależne lub używane obok siebie, użyj INDEX_.
  3. Dla każdego wpisu utwórz nową zmienną lokalną, której nazwa zaczyna się od ENUM_lub INDEX_, następnie nazwa grupy, następnie podkreślenie, a następnie unikalna przyjazna nazwa właściwości
  4. Dodać ENUMLENGTH_, ENUMLEN_, INDEXLENGTH_, lub INDEXLEN_(niezależnie od tego LEN_czy LENGTH_jest preferencje osobiste) Zmienna wyliczone na samym końcu. Powinieneś używać tej zmiennej tam, gdzie to możliwe w swoim kodzie, aby mieć pewność, że dodanie dodatkowego wpisu do wyliczenia i zwiększenie tej wartości nie spowoduje uszkodzenia kodu.
  5. Daj każdej kolejnej zmiennej wyliczone wartości o jeden więcej niż w ubiegłym, zaczynając od 0. Istnieje komentarze na tej stronie, powiedzmy 0nie powinny być stosowane jako Numeracja dlatego 0 == null, 0 == false, 0 == ""i inne szaleństwa JS. Przekazuję Ci, że aby uniknąć tego problemu i jednocześnie zwiększyć wydajność, zawsze używaj ===i nigdy nie pozwól, aby ==pojawiał się w kodzie, z wyjątkiem typeof(ex typeof X == "string"). Przez wszystkie lata użytkowania ===nigdy nie miałem problemu z użyciem 0 jako wartości wyliczenia. Jeśli nadal jesteś wrażliwy, 1może być używany jako wartość początkowa w ENUM_wyliczeniach (ale nie w INDEX_wyliczeniach) bez utraty wydajności w wielu przypadkach.
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
}

Oto, jak pamiętam, kiedy INDEX_i kiedy użyć ENUM_:

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

Jednakże, ENUM_może w pewnych okolicznościach być odpowiednie jako wskaźnik taki jak przy liczeniu liczby wystąpień każdego elementu.

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

Zauważ, że w powyższym kodzie naprawdę łatwo jest dodać nowy rodzaj zwierzaka: wystarczy dodać nowy wpis ENUM_PET_RATi ENUMLEN_PETodpowiednio go zaktualizować . Dodanie nowego wpisu w innych systemach wyliczania może być trudniejsze i bardziej problematyczne.


wvwwvw wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvwv vwvxwvw wvwvwvwvwvwvwwwww

𝗘𝘅𝘁𝗲𝗻𝗱 𝗨𝗽𝗽𝗲𝗿𝗰𝗮𝘀𝗲 𝗩𝗮𝗿𝗶𝗮𝗯𝗹𝗲𝘀 𝗪𝗶𝘁𝗵 𝗔𝗱𝗱𝗶𝘁𝗶𝗼𝗻

Ponadto ta składnia wyliczeń pozwala na jasne i zwięzłe rozszerzanie klas, jak pokazano poniżej. Aby przedłużyć klasę, dodaj rosnący numer do LEN_pozycji klasy nadrzędnej. Następnie zakończ podklasę własnym LEN_wpisem, aby podklasa mogła zostać w przyszłości rozszerzona.

Schemat rozszerzenia dodawania

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

(Długość: 2450 bajtów)

Niektórzy mogą powiedzieć, że jest to mniej praktyczne niż inne rozwiązania: zwalnia mnóstwo miejsca, zajmuje dużo czasu do pisania i nie jest pokryte składnią cukrową. Ci ludzie mieliby rację, gdyby nie zminimalizowali swojego kodu. Jednak żadna rozsądna osoba nie zostawiłaby nieuprawnionego kodu w produkcie końcowym. Do tej minimalizacji Kompilator Zamknięcia jest najlepszym, jaki jeszcze nie znalazłem. Dostęp online można znaleźć tutaj . Kompilator zamykania jest w stanie pobrać wszystkie dane wyliczenia i wstawić je, dzięki czemu JavaScript jest bardzo mały i szybko działa. Dlatego Minify z kompilatorem zamknięcia. Przestrzegać.


wvwwvw wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvwv vwvxwvw wvwvwvwvwvwvwwwww

𝗠𝗶𝗻𝗶𝗳𝘆 𝗪𝗶𝘁𝗵 𝗖𝗹𝗼𝘀𝘂𝗿𝗲 𝗖𝗼𝗺𝗽𝗶𝗹𝗲𝗿

Kompilator Closure jest w stanie przeprowadzić pewne niewiarygodne optymalizacje za pomocą wnioskowania, które wykraczają daleko poza możliwości jakiegokolwiek innego minifigatora JavaScript. Kompilator zamknięcia może wstawiać pierwotne zmienne ustawione na stałą wartość. Kompilator zamknięcia może także dokonywać wnioskowania na podstawie tych wartości wprowadzonych i eliminować nieużywane bloki w instrukcjach if i pętlach.

Kod wykręcania za pomocą kompilatora zamknięcia

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

(Długość: 605 bajtów)

Closure Compiler nagradza cię za inteligentniejsze kodowanie i porządne porządkowanie kodu, ponieważ podczas gdy wielu minifikatorów karze zorganizowany kod większym zmniejszonym rozmiarem pliku, Closure Compiler jest w stanie przesiać całą twoją czystość i rozsądek, aby uzyskać jeszcze mniejszy rozmiar pliku, jeśli używasz sztuczek jak wyliczenia nazw zmiennych. To, w tym jednym umyśle, jest święty graal kodowania: narzędzie, które zarówno wspomaga twój kod przy mniejszym pomniejszonym rozmiarze, jak i pomaga twojemu umysłowi, trenując lepsze nawyki programistyczne.


wvwwvw wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvwv vwvxwvw wvwvwvwvwvwvwwwww

𝗦𝗺𝗮𝗹𝗹𝗲𝗿 𝗖𝗼𝗱𝗲 𝗦𝗶𝘇𝗲

Zobaczmy teraz, jak duży byłby równoważny plik bez tych wyliczeń.

Źródło bez
użycia wyliczeń (długość: 1973 bajtów (477 bajtów krótszych niż wyliczony kod!)) Zminimalizowane bez użycia wyliczeń (długość: 843 bajtów (238 bajtów dłuższych niż wyliczony kod ))

Tabela rozmiarów kodów



Jak widać, bez wyliczeń kod źródłowy jest krótszy kosztem większego zminimalizowanego kodu. Nie wiem jak ty; ale wiem na pewno, że nie dołączam kodu źródłowego do produktu końcowego. Dlatego ta forma wyliczania jest znacznie lepsza, ponieważ powoduje mniejsze mniejsze pliki.


wvwwvw wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvwv vwvxwvw wvwvwvwvwvwvwwwww

𝗖𝗼𝗼𝗽𝗲𝗿𝗮𝘁𝗶𝘃𝗲 🤝 𝗕𝘂𝗴 𝗙𝗶𝘅𝗶𝗻𝗴

Kolejną zaletą tej formy wyliczania jest to, że można jej użyć do łatwego zarządzania projektami na dużą skalę bez poświęcania zminimalizowanego rozmiaru kodu. Podczas pracy nad dużym projektem z wieloma innymi osobami, może być korzystne wyraźne oznaczenie i oznaczenie nazw zmiennych, kto utworzył kod, aby można było szybko zidentyfikować oryginalnego twórcę kodu w celu wspólnego usuwania błędów.

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

𝗦𝘂𝗽𝗲𝗿𝗶𝗼𝗿 𝗣𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲

Ponadto ta forma wyliczenia jest również znacznie szybsza po zminimalizowaniu. W normalnych nazwanych właściwościach przeglądarka musi używać skrótów, aby sprawdzić, gdzie właściwość znajduje się na obiekcie. Chociaż kompilatory JIT inteligentnie buforują tę lokalizację na obiekcie, nadal występują ogromne koszty ogólne ze względu na specjalne przypadki, takie jak usunięcie niższej właściwości z obiektu.

Ale dzięki ciągłym, nierzadkim macierzom PACKED_ELEMENTS z indeksowaniem liczb całkowitych , przeglądarka może pominąć znaczną część tego narzutu, ponieważ indeks wartości w tablicy wewnętrznej jest już określony. Tak, zgodnie ze standardem ECMAScript wszystkie właściwości powinny być traktowane jako ciągi znaków. Niemniej jednak ten aspekt standardu ECMAScript jest bardzo mylący pod względem wydajności, ponieważ wszystkie przeglądarki mają specjalne optymalizacje dla indeksów numerycznych w tablicach.

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

Porównaj powyższy kod z kodem poniżej.

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

Można sprzeciwić się kodowi z wyliczeniami, które wydają się znacznie dłuższe niż kod ze zwykłymi obiektami, ale wygląd może być mylący. Ważne jest, aby pamiętać, że rozmiar kodu źródłowego nie jest proporcjonalny do rozmiaru wyjściowego podczas korzystania z epickiego kompilatora Closure. Przestrzegać.

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

Zminimalizowany kod bez wyliczeń znajduje się powyżej, a zminimalizowany kod z wyliczeniami znajduje się poniżej.

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

Powyższy przykład pokazuje, że oprócz doskonałej wydajności, wyliczony kod skutkuje również mniejszym zminimalizowanym rozmiarem pliku.


wvwwvw wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvwv vwvxwvw wvwvwvwvwvwvwwwww

𝗘𝗮𝘀𝘆 𝗗𝗲𝗯𝘂𝗴𝗴𝗶𝗻𝗴

Co więcej, osobista wiśnia tego na górze korzysta z tej formy wyliczania wraz z edytorem tekstu CodeMirror w trybie Javascript. Tryb podświetlania składni JavaScript CodeMirror wyróżnia zmienne lokalne w bieżącym zakresie. W ten sposób od razu wiesz, kiedy poprawnie wpiszesz nazwę zmiennej, ponieważ jeśli nazwa zmiennej została wcześniej zadeklarowana varsłowem kluczowym, wówczas nazwa zmiennej zmienia kolor na specjalny (domyślnie błękitny). Nawet jeśli nie korzystasz z CodeMirror, przynajmniej przeglądarka rzuca pomocne[variable name] is not definedwyjątek podczas wykonywania kodu z błędnie wpisanymi nazwami wyliczeń. Ponadto narzędzia JavaScript, takie jak JSLint i kompilator zamknięcia, bardzo głośno mówią o błędach w nazwie zmiennej wyliczeniowej. CodeMirror, przeglądarka i różne narzędzia JavaScript razem sprawiają, że debugowanie tej formy wyliczenia jest bardzo proste i bardzo łatwe.

CodeMirror wyróżnia demonstrację

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
}

W powyższym fragmencie został wyświetlony komunikat o błędzie, ponieważ ENUM_COLORENUM_DNEnie istnieje.


wvwwvw wvwvwvw wvwxvw wvwvwv vwvwvw wvwvvw wvwwvw wvwvwvw wvwvw wvwvwv vwvxwvw wvwvwvwvwvwvwwwww

𝗖𝗼𝗻𝗰𝗹𝘂𝘀𝗶𝗼𝗻 ☑

Myślę, że można bezpiecznie powiedzieć, że ta metodologia wyliczania jest rzeczywiście najlepszym sposobem, aby przejść nie tylko do zminimalizowania rozmiaru kodu, ale także do wydajności, debugowania i współpracy.

Po przeczytaniu pomocnego pytania dziękuję autorowi za poświęcenie czasu na pisanie, klikając lewą górną strzałkę w górę w polu pytania. Każde pole odpowiedzi ma także jedną z tych strzałek w górę.


Eh Zdecydowanie wolę czytelność, łatwość użycia i zrozumienie niż rozmiar kodu.
Andrew

1
@Andrew Dzięki mojej odpowiedzi możesz mieć jedno i drugie. Moja odpowiedź skutkuje najłatwiejszym w użyciu / zarządzaniem kodem i najmniejszym zminimalizowanym rozmiarem kodu. ”
Jack Giffin

1
@Andrew Próbowałem zastosować twój Yet Another Enum (YEA!) Do przykładu parsera kolorów w mojej odpowiedzi. Znalazłem jednak kilka problemów, które możesz rozwiązać. YEA nie ma możliwości rozszerzenia wyliczeń o podklasy, zmuszając mnie do tworzenia oddzielnych klas nadrzędnych i podrzędnych, co może być trudne do zarządzania w dużych projektach. YEA nie zapewnia, że ​​wpis istnieje (bez colors.REEDzbiorów undefined), więc literówki tworzą nieuchwytne zagadki. YEA nie rozróżnia użycia wyliczeń jako indeksów i identyfikatorów, co prowadzi do mylącego kodu, w którym wszystko wygląda tak samo. …
Jack Giffin

1
@Andrew… YEA utrudnia zdolność kompilatora Closure do minimalizacji. Porównaj kod źródłowy z YEA (3549 bajtów) z kodem zminimalizowanym z YEA (1344 bajtów) z kodem zminimalizowanym z moim rozwiązaniem (604 bajty). Wreszcie, YEA obejmuje „mapowanie według nazwy”, ponieważ oddziela nazwy ciągów od wyliczonych identyfikatorów. Mój uwzględnia tylko ID, więc nie jest potrzebne „mapowanie według nazwy”, co prowadzi do prostszego projektu i lepszej wydajności. Dziękujemy za udostępnienie rozwiązania, ale zanim będzie to praktyczne, wymaga wielu poprawek.
Jack Giffin

1
@Andrew Masz prawo do mojej opinii, tak jak ja do mojej 👍
Jack Giffin

23

Bawiłem się tym, ponieważ uwielbiam moje wyliczenia. =)

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

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

Korzystając z tej metody ... powinieneś (teoretycznie) móc wywoływać i definiować 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 powinienwritable:false to zrobić zrobić to wpisać bezpieczne.

Powinieneś być w stanie stworzyć niestandardowy obiekt, a następnie wywołać Enum()go. Przypisane wartości zaczynają się od 0 i przyrost na sztukę.

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

3
Jeśli dodasz return this;na końcu Enum, możesz zrobić:var EnumColors = {}.Enum('RED','BLUE','GREEN','YELLOW');
HBP

Nie brałem tego pod uwagę, ponieważ nie jest to moja normalna metoda robienia rzeczy. Ale masz całkowitą rację! Zmienię to w.
Duncan,

Bardzo mi się to podoba, chociaż nie jestem wielkim fanem niszczenia przestrzeni obiektów (z globalną funkcją ENUM). Przekształciłem to w funkcję mkenum i dodałem opcjonalne przypisania numeryczne => var mixedUp = mkenum („CZARNY”, {CZERWONY: 0x0F00, NIEBIESKI: 0X0F, ZIELONY: 0x0F0, BIAŁY: 0x0FFF, JEDEN: 1}, DWIE, TRZY, CZTERY) ; // Dodanie mojego kodu jako odpowiedzi poniżej. Dzięki.
Andrew Philips,

Szczerze mówiąc, nawet tego nie używam. Korzystam z kompilatora zamykania Google, a to nie działa zbyt dobrze (lub tylko to komplikuje), jeśli używasz ustawienia Zaawansowane. Właśnie wróciłem do standardowej notacji obiektowej.
Duncan

1
falsejest domyślną writable, enumerablea configurable. Nie ma potrzeby żucia nad domyślnymi.
ceving

23

Użyj Javascript Proxy

TLDR: Dodaj tę klasę do metod narzędziowych i używaj jej w całym kodzie, wyśmiewa zachowanie Enum z tradycyjnych języków programowania i faktycznie generuje błędy, gdy próbujesz uzyskać dostęp do nieistniejącego modułu wyliczającego lub dodać / zaktualizować moduł wyliczający. Nie musisz polegać 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);
  }
}

Następnie utwórz wyliczenia, tworząc instancję klasy:

const roles = new Enum({
  ADMIN: 'Admin',
  USER: 'User',
});

Pełne objaśnienie:

Jedną z bardzo korzystnych cech Enums, które otrzymujesz z tradycyjnych języków, jest to, że wysadzają w powietrze (generują błąd czasu kompilacji), jeśli spróbujesz uzyskać dostęp do modułu wyliczającego, który nie istnieje.

Oprócz zamrożenia wyśmiewanej struktury wyliczeniowej, aby zapobiec przypadkowemu / złośliwemu dodaniu dodatkowych wartości, żadna z pozostałych odpowiedzi nie dotyczy tej nieodłącznej cechy Enums.

Jak zapewne wiesz, dostęp do nieistniejących członków w JavaScript po prostu powraca undefined i nie wysadza twojego kodu. Ponieważ moduły wyliczające są predefiniowanymi stałymi (tj. Dniami tygodnia), nigdy nie powinno być przypadku, gdy moduł wyliczający powinien być niezdefiniowany.

Nie zrozumcie mnie źle, zachowanie JavaScript powrotu undefinedpodczas uzyskiwania dostępu do nieokreślonych właściwości jest w rzeczywistości bardzo potężną funkcją języka, ale nie jest to funkcja, której chcesz, gdy próbujesz kpić z tradycyjnych struktur Enum.

To tutaj świecą obiekty proxy. Serwer proxy ustandaryzowano w języku wraz z wprowadzeniem ES6 (ES2015). Oto opis z MDN:

Obiekt Proxy służy do definiowania niestandardowego zachowania dla podstawowych operacji (np. Wyszukiwanie właściwości, przypisywanie, wyliczanie, wywoływanie funkcji itp.).

Podobnie jak proxy serwera WWW, proxy JavaScript mogą przechwytywać operacje na obiektach (za pomocą „pułapek”, w razie potrzeby nazywać je hakami) i umożliwiać wykonywanie różnych kontroli, akcji i / lub manipulacji przed ich zakończeniem (lub w niektórych przypadkach całkowite zatrzymanie operacji, co dokładnie chcemy zrobić, jeśli i kiedy spróbujemy odwołać się do modułu wyliczającego, który nie istnieje).

Oto wymyślony przykład, który używa obiektu Proxy do naśladowania Enums. Moduł wyliczający w tym przykładzie to standardowe metody HTTP (tj. „GET”, „POST” itp.):

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


NA BOK: Co to do cholery jest proxy?

Pamiętam, kiedy po raz pierwszy zobaczyłem słowo proxy wszędzie, zdecydowanie nie miało to dla mnie sensu przez długi czas. Jeśli tak, to uważam, że łatwym sposobem na uogólnienie serwerów proxy jest myślenie o nich jak o oprogramowaniu, instytucjach, a nawet ludziach, którzy działają jako pośrednicy lub pośrednicy między dwoma serwerami, firmami lub ludźmi.


Jak zrobić coś takiego jak myEnum.valueOf („someStringValue”)? Oczekiwany: w przypadku gdy łańcuch wejściowy ma wartość elementu modułu wyliczającego, powinien zwrócić element. W przypadku, gdy żaden element nie ma takiej wartości ciągu, wyrzuć wyjątek.
sscarduzio

@ scarduzio można zastąpić domyślną valueOfmetodę, określając ją jako metodę instancji w klasie Enum. Dlaczego jednak chcesz uzyskać do niego dostęp w ten sposób, a nie tylko poprzez notację kropkową?
Govind Rai

Moje wyliczenie to const logLevelEnum = nowe wyliczenie ({INFO: „info”, DEBUG: „debug”}) i analizuję od wprowadzenia dowolnego ciągu „info” lub „debug”. Potrzebuję więc czegoś takiego jak currentLogLevel = logLevelEnum.parseOrThrow (settings.get ("log_level"))
sscarduzio

1
Dlaczego po prostu nie mogłeś logLevelEnum[settings.get("log_level")]? dodawanie parseOrThrowbyłoby po prostu powtarzalne do tego, co pułapki proxy już dla ciebie robią.
Govind Rai

17

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

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

Umożliwia to sprawdzenie, MyEnum.Barktóre zwracają 1, a MyEnum[1]które zwracają „Pasek” niezależnie od kolejności deklaracji.


1
Plus MyEnum [„Bar”] działa, który zwraca 1 ... <3 TypeScript do tej pory ...
David Karlaš

3
i oczywiście, jeśli faktycznie używasz maszynopisu:enum MyEnum { Foo, Bar, Foobar }
parlament

16

W ES7 możesz wykonać elegancki ENUM w oparciu o atrybuty statyczne:

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

następnie

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

Zaletą (używania klasy zamiast dosłownego obiektu) jest posiadanie klasy nadrzędnej, Enumwtedy wszystkie twoje Enumy rozszerzą tę klasę.

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

4
Czy mógłbyś wyjaśnić, dlaczego posiadanie klasy dla rodziców jest zaletą? Czuję, że czegoś mi brakuje!
Jon G

7
Nie rób tego new ColorEnum()nie ma absolutnie żadnego sensu.
Bergi,

3
przedłużanie wyliczenia brzmi naprawdę szalone
Codii

gdy język nie obsługuje go natywnie, warto zachować tę konwencję i używać jej w ten sposób! zgadzam się!
xpto

Myślę, że (?), Do czego zmierza OP: Zaletą czystej elektryczności statycznej jest to, że jest dostępna wszędzie jako singleton i nie musisz tworzyć instancji klasy - OP nie sugeruje, abyś to zrobił! Myślę, że to, co on mówi jest to, że nadklasą Enumposiada standardowe statyczne metody wyliczający na to, jak getValues(), getNames(), iterate(), itd. Jeśli o to chodzi, nie masz do reimplement je dla każdego nowego rodzaju enum.
Inżynier

15

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 ostatnich 2 metod do mapowania wyliczeń z obiektów wiadomości.

Niektóre zalety tego podejścia:

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

Niektóre wady:

  • Tam dzieje się trochę niechlujnego zarządzania pamięcią, ponieważ przechowuję odniesienia do wyliczeń
  • Nadal nie ma bezpieczeństwa typu

14

Utwórz literał obiektu:

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

12
constnie sprawia, że ​​właściwości obiektu są niezmienne, oznacza tylko, że zmiennej Modesnie można przypisać do czegoś innego. Aby uczynić go bardziej kompletnym, użyj Object.freeze()obok const.
rvighne

Proszę nie używać Object.freeze. Zapobiega to wstawianiu obiektu przez kompilator zamknięcia.
Jack Giffin,

11

Jeśli używasz Backbone , możesz uzyskać pełną funkcjonalność wyliczania (znaleźć według identyfikatora, nazwy, niestandardowych członków) za darmo, korzystając z 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()

8

twoje odpowiedzi są zbyt skomplikowane

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

1
@JackGiffin Zgadzam się, że twoja odpowiedź jest bardziej wydajna i że moja może zająć więcej pamięci, chociaż nie powinieneś zakładać, że każdy chce wyliczenia w sposób, w jaki C ++ go implementował. Szanuj inne odpowiedzi i programistów, którzy mogą preferować tę odpowiedź niż twoją.
Xeltor

7

Zmodyfikowałem rozwiązanie 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);
    }
  }

Test:

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

6

Wymyśliłem to podejście, które jest wzorowane na wyliczeniach w Javie. Są one bezpieczne dla typu, więc możesz wykonaćinstanceof kontrole.

Możesz zdefiniować wyliczenia w następujący sposób:

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

Days teraz odnosi się do Days wyliczenia:

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"

Implementacja:

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
    }

})();

Wygląda ładnie, może powinieneś sprawdzić, czy istnieje freezemetoda zgodności wstecznej? Np.if (Object.freeze) { Object.freeze(values); }
FBB

Słuszna uwaga! Zrobi!
Vivin Paliath,

6

IE8 nie obsługuje metody freeze ().
Źródło: http://kangax.github.io/compat-table/es5/ , Kliknij „Pokaż przestarzałe przeglądarki?” u góry i sprawdź IE8 i zamroź przecięcie wiersza col.

W moim obecnym projekcie gry wykorzystałem poniżej, ponieważ niewielu klientów nadal korzysta z IE8:

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

Możemy również zrobić:

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

a nawet to:

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

Ten ostatni, wydaje się najbardziej wydajny dla łańcucha, zmniejsza całkowitą przepustowość, jeśli serwer i klient wymieniają te dane.
Oczywiście teraz Twoim obowiązkiem jest upewnienie się, że nie ma konfliktów w danych (RE, EX itp. Muszą być unikalne, również 1, 2 itp. Powinny być unikalne). Pamiętaj, że musisz zachować je na zawsze, aby zachować zgodność wsteczną.

Zadanie:

var wildType = CONST_WILD_TYPES.REGULAR;

Porównanie:

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

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

Nie musisz się upewnić, że w ten sposób nie przypisujesz duplikatów różnym wartościom wyliczenia. Nowy obiekt jest tworzony w postaci instancji i przypisywany do wszystkich wartości wyliczania.


Ta odpowiedź jest niedoceniana. To jeden z moich ulubionych pomysłów na swoją prostotę. W praktyce myślę, że będę trzymać się ciągów, ponieważ na razie łatwiej jest debugować.
Domino,

Hmm, tylko upewnij się, że ten kod nie zostanie wywołany dwukrotnie ...
Andrew

4

Oto kilka różnych sposobów implementacji wyliczeń TypeScript .

Najłatwiej jest po prostu iterować obiekt, dodając do niego odwrócone pary klucz-wartość. Jedyną wadą jest to, że musisz ręcznie ustawić wartość dla każdego elementu.

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


A oto mixina lodash, aby utworzyć wyliczenie za pomocą łańcucha. Ta wersja jest nieco bardziej zaangażowana, ale automatycznie wykonuje numerację. Wszystkie metody lodash zastosowane w tym przykładzie mają zwykły odpowiednik JavaScript, więc możesz je łatwo zmienić, jeśli chcesz.

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

bardzo sprytne, dzięki
Ilan

4

Właśnie opublikowałem pakiet NPM gen_enum umożliwia szybkie tworzenie struktury danych Enum w 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 

Jedną fajną rzeczą w tym małym narzędziu jest to, że w nowoczesnym środowisku (w tym w przeglądarkach nodejs i IE 9+) zwracany obiekt Enum jest niezmienny.

Aby uzyskać więcej informacji, sprawdź https://github.com/greenlaw110/enumjs

Aktualizacje

Ja przestarzała gen_enum pakiet i scalam funkcję w pakiet constjs , który zapewnia więcej funkcji, w tym niezmienne obiekty, deserializację ciągów JSON, stałe ciągów i generowanie bitmap itp. Kasa https://www.npmjs.com/package/constjs Więcej informacji na

Aby uaktualnić gen_enumdo constjswystarczy zmienić oświadczenie

var genEnum = require('gen_enum');

do

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

4

Najprostsze rozwiązanie:

Stwórz

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

Uzyskaj wartość

console.log(Status.Ready) // 1

Weź klucz

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

4

Stworzyłem klasę Enum, która może pobierać wartości ORAZ nazwy w O (1). Może także generować tablicę obiektów zawierającą wszystkie nazwy i wartości.

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

Możesz to zainicjować w następujący sposób:

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

Aby pobrać wartość (jak Enums w C #):

var val2 = enum1.item2;

Aby pobrać nazwę dla wartości (może być niejednoznaczny przy wprowadzaniu tej samej wartości dla różnych nazw):

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

Aby uzyskać tablicę z każdą nazwą i wartością w obiekcie:

var arr = enum1.GetObjArr();

Wygeneruje:

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

Możesz również łatwo uzyskać opcje wyboru HTML:

var html = enum1.GetSelectOptionsHTML();

Który zawiera:

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

4

Mimo że w ES2015 obsługiwane są tylko metody statyczne (a nie właściwości statyczne) (patrz także tutaj , §15.2.2.2), co ciekawe, możesz użyć poniższej wersji z Babel ze wstępnym es2015ustawieniem:

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

CellStatePrzekonałem się, że działa to zgodnie z oczekiwaniami, nawet między modułami (np. Importując wyliczenie z innego modułu), a także podczas importowania modułu za pomocą Webpacka.

Zaletą tej metody w porównaniu z większością innych odpowiedzi jest to, że można jej używać wraz ze sprawdzaniem typu statycznego (np. Flow ), a także w trakcie opracowywania przy użyciu sprawdzania typu statycznego można stwierdzić, że zmienne, parametry itp. Są określoneCellState „ enum ”zamiast jakiegoś innego wyliczenia (którego nie można byłoby odróżnić, jeśli użyjesz ogólnych obiektów lub symboli).

aktualizacja

Powyższy kod ma wadę polegającą na tym, że pozwala tworzyć dodatkowe obiekty typu CellState(nawet jeśli nie można ich przypisać do pól statycznych, CellStateponieważ jest zamrożony). Mimo to poniższy, bardziej dopracowany kod oferuje następujące zalety:

  1. nigdy więcej obiektów typu CellState można już tworzyć
  2. masz gwarancję, że do dwóch wystąpień enum nie zostanie przypisany ten sam kod
  3. metoda użyteczna do odzyskania wyliczenia z reprezentacji ciągu
  4. valuesfunkcja, która zwraca wszystkie instancje wyliczenia nie musi tworzyć wartość zwracaną w powyższym, ręcznym (i podatne na błędy) sposób.

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

Dobry przykład :-)
Ashraf.Shk786

4

sposób es7, (iterator, zamrażanie), użycie:

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'

kod:

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(', ') }

}

4

Oto w jaki sposób maszynopis tłumaczy to enumna JavaScript:

var makeEnum = function(obj) {
    obj[ obj['Active'] = 1 ] = 'Active';
    obj[ obj['Closed'] = 2 ] = 'Closed';
    obj[ obj['Deleted'] = 3 ] = 'Deleted';
}

Teraz:

makeEnum( NewObj = {} )
// => {1: "Active", 2: "Closed", 3: "Deleted", Active: 1, Closed: 2, Deleted: 3}

Na początku byłem zdezorientowany, dlaczego obj[1]zwraca 'Active', ale potem zdałem sobie sprawę, że jest to bardzo proste - operator przypisania przypisuje wartość, a następnie zwraca:

obj['foo'] = 1
// => 1

4

Możesz zrobić coś takiego

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

Jak zdefiniowano w tej bibliotece. https://github.com/webmodule/foo/blob/master/foo.js#L217

Kompletny przykład https://gist.github.com/lnt/bb13a2fd63cdb8bce85fd62965a20026


3

Szybki i prosty sposób to:

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

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

6
Ta funkcja jest niepotrzebna i daje dokładnie taki sam wynik, jak opublikowany PO.
Sildoreth,

3

Stan na piśmie, październik 2014 r - oto współczesne rozwiązanie. Piszę rozwiązanie jako moduł węzła, i zawarłem test z użyciem Mocha i Chai, a także podkreślenie JS. Możesz łatwo je zignorować i po prostu wziąć kod Enum, jeśli wolisz.

Widziałem wiele postów z nadmiernie skomplikowanymi bibliotekami itp. Rozwiązanie w celu uzyskania obsługi enum w Javascript jest tak proste, że tak naprawdę nie jest potrzebne. Oto kod:

Plik: 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;

I test ilustrujący to, co daje:

plik: 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');
        });
    });

});

Jak widać, masz fabrykę Enum, możesz zdobyć wszystkie klucze, po prostu wywołując enum.keys, i możesz dopasować same klucze do stałych całkowitych. Możesz ponownie wykorzystać fabrykę z różnymi wartościami i wyeksportować wygenerowane wyliczenia za pomocą modułowego podejścia Node.

Jeszcze raz, jeśli jesteś zwykłym użytkownikiem lub w przeglądarce itp., Po prostu weź fabryczną część kodu, potencjalnie usuwając bibliotekę podkreślenia, jeśli nie chcesz używać go w kodzie.


5
Czy możesz napisać odpowiedź, mówiąc „oto, jak to zrobić jako zwykły użytkownik, który chce tylko wyliczeń, a nie fabryk, podkreśleń lub czegoś wymyślnego”?
GreenAsJade

5
Mimo, że jest to całkiem niesamowite z punktu widzenia programisty, nie jest zbyt czyste ani czytelne. Rozwiązanie Enum z OP jest łatwiejsze i bardziej czytelne pod każdym względem, a zatem jest lepsze w użyciu. Mimo to całkiem nieźle to wymyśliłeś.
David

3

Myślę, że jest łatwy w użyciu. 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);

AKTUALIZACJA:

Tam są moje kody pomocnicze ( TypeHelper).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.