Enums in Javascript mit ES6

Lesezeit: 7 Minuten

Benutzer-Avatar
Chiffr

Ich baue ein altes Java-Projekt in Javascript neu auf und habe festgestellt, dass es keine gute Möglichkeit gibt, Enumerationen in JS durchzuführen.

Das Beste was mir einfällt ist:

const Colors = {
    RED: Symbol("red"),
    BLUE: Symbol("blue"),
    GREEN: Symbol("green")
};
Object.freeze(Colors);

Das const hält Colors nicht neu zugewiesen werden, und das Einfrieren verhindert, dass die Schlüssel und Werte verändert werden. Ich verwende dafür Symbole Colors.RED ist ungleich zu 0oder irgendetwas anderes außer sich.

Gibt es ein Problem mit dieser Formulierung? Gibt es einen besseren Weg?


(Ich weiß, dass diese Frage ein bisschen wiederholt wird, aber alle vorherigen Fragen und Antworten sind ziemlich alt, und ES6 gibt uns einige neue Möglichkeiten.)


BEARBEITEN:

Eine andere Lösung, die sich mit dem Serialisierungsproblem befasst, aber meiner Meinung nach immer noch Bereichsprobleme hat:

const enumValue = (name) => Object.freeze({toString: () => name});

const Colors = Object.freeze({
    RED: enumValue("Colors.RED"),
    BLUE: enumValue("Colors.BLUE"),
    GREEN: enumValue("Colors.GREEN")
});

Durch die Verwendung von Objektreferenzen als Werte erhalten Sie die gleiche Kollisionsvermeidung wie bei Symbolen.

  • Dies wäre ein perfekter Ansatz in es6 . Du musst es nicht einfrieren

    – NiRUS

    9. Juni 2017 um 1:35


  • @Nirus tun Sie, wenn Sie nicht möchten, dass es geändert wird.

    – zerkms

    9. Juni 2017 um 1:37 Uhr

  • Haben Sie diese Antwort bemerkt?

    – Bergi

    9. Juni 2017 um 1:47 Uhr

  • Ein Problem, das mir einfällt: Kann diese Aufzählung nicht verwenden JSON.stringify(). Kann nicht serialisieren/deserialisieren Symbol.

    – le_m

    9. Juni 2017 um 12:27 Uhr

  • @ErictheRed Ich verwende seit Jahren problemlos String-Enum-Konstantenwerte, da die Verwendung von Flow (oder TypeScript) viel mehr Typsicherheit garantiert, als sich jemals über die Kollisionsvermeidung Gedanken zu machen

    – Andy

    19. Dezember 2018 um 2:21 Uhr

Benutzer-Avatar
Bergi

Gibt es ein Problem mit dieser Formulierung?

Ich sehe keine.

Gibt es einen besseren Weg?

Ich würde die beiden Aussagen zu einer zusammenfassen:

const Colors = Object.freeze({
    RED:   Symbol("red"),
    BLUE:  Symbol("blue"),
    GREEN: Symbol("green")
});

Wenn Sie die Boilerplate nicht mögen, mögen Sie die Wiederholung Symbol Aufrufe können Sie natürlich auch eine Hilfsfunktion schreiben makeEnum die dasselbe aus einer Liste von Namen erstellt.

  • Gibt es hier keine Realm-Probleme?

    Benutzer663031

    9. Juni 2017 um 3:28

  • @torazaburo Du meinst, wenn der Code zweimal geladen wird, werden unterschiedliche Symbole generiert, was bei Zeichenfolgen kein Problem wäre? Ja, guter Punkt, mach eine Antwort 🙂

    – Bergi

    9. Juni 2017 um 3:44 Uhr

  • @ErictheRed Nein, Symbol.for tut nicht haben Cross-Realm-Probleme, jedoch das übliche Kollisionsproblem mit einem wirklich globalen Namespace.

    – Bergi

    13. Juni 2017 um 0:11 Uhr


  • @ErictheRed Es garantiert in der Tat, genau das gleiche Symbol zu erstellen, unabhängig davon, wann und wo (von welchem ​​Bereich/Frame/Tab/Prozess) es aufgerufen wird

    – Bergi

    23. Juni 2017 um 5:34 Uhr

  • @Sky Ein Standardwert für die Suche in Colors hat nichts mit der Enum-Definition zu tun. Das würde man wie gewohnt mit machen Colors[name] || Colors.BLUE oder Colors.hasOwnProperty(name) ? Colors[name] : Colors.BLUE.

    – Bergi

    12. Februar 2019 um 17:02 Uhr

Benutzer-Avatar
Justin Emery

Während der Verwendung Symbol Da der Enum-Wert für einfache Anwendungsfälle gut funktioniert, kann es praktisch sein, Enumerationen Eigenschaften zuzuweisen. Dies kann mit einem erfolgen Object als Aufzählungswert, der die Eigenschaften enthält.

Zum Beispiel können wir jedem der geben Colors Name und Hex-Wert:

/**
 * Enum for common colors.
 * @readonly
 * @enum {{name: string, hex: string}}
 */
const Colors = Object.freeze({
  RED:   { name: "red", hex: "#f00" },
  BLUE:  { name: "blue", hex: "#00f" },
  GREEN: { name: "green", hex: "#0f0" }
});

Das Einschließen von Eigenschaften in die Aufzählung vermeidet das Schreiben switch -Anweisungen (und möglicherweise neue Fälle in den switch-Anweisungen vergessen, wenn eine Aufzählung erweitert wird). Das Beispiel zeigt auch die mit dokumentierten Aufzählungseigenschaften und -typen JSDoc-Enum-Anmerkung.

Gleichstellung funktioniert wie erwartet mit Colors.RED === Colors.RED Sein trueund Colors.RED === Colors.BLUE Sein false.

  • Beachten Sie, dass Object.freeze nicht tief ist, Sie könnten zB Colors.RED.name = “charlie”; Sie könnten so etwas verwenden, um verschachtelte Objekte einzufrieren: const deepFreeze = obj => { Object.keys(obj).forEach(prop => { if (typeof obj[prop] === ‘Objekt’) deepFreeze(obj[prop]); }); Rückgabe von Object.freeze (obj); };

    – Steinspitze

    5. Juli um 19:37 Uhr


Das ist mein persönlicher Ansatz.

class ColorType {
    static get RED () {
        return "red";
    }

    static get GREEN () {
        return "green";
    }

    static get BLUE () {
        return "blue";
    }
}

// Use case.
const color = Color.create(ColorType.RED);

  • Ich würde dies nicht empfehlen, da es keine Möglichkeit bietet, über alle möglichen Werte zu iterieren, und keine Möglichkeit, zu überprüfen, ob ein Wert ein ColorType ist, ohne manuell nach jedem zu suchen.

    – Dominostein

    6. April 2020 um 7:10 Uhr

  • Ich fürchte, das ist zu viel Code, um einen Enum-Typ zu definieren, der sehr kurz sein sollte

    – Zhe

    5. Januar um 15:16 Uhr

Wie oben erwähnt, könnte man auch a schreiben makeEnum() Hilfsfunktion:

function makeEnum(arr){
    let obj = {};
    for (let val of arr){
        obj[val] = Symbol(val);
    }
    return Object.freeze(obj);
}

Verwenden Sie es wie folgt:

const Colors = makeEnum(["red","green","blue"]);
let startColor = Colors.red; 
console.log(startColor); // Symbol(red)

if(startColor == Colors.red){
    console.log("Do red things");
}else{
    console.log("Do non-red things");
}

Wenn Sie nicht brauchen rein ES6 und kann Typescript verwenden, es hat eine nette enum:

https://www.typescriptlang.org/docs/handbook/enums.html

Aktualisierung 11.05.2020:

Geändert, um statische Felder und Methoden einzuschließen, um das “echte” Enum-Verhalten näher zu replizieren.

Wenn Sie eine Aktualisierung planen, würde ich empfehlen, zu versuchen, eine sogenannte “Enum-Klasse” zu verwenden (mit Ausnahme von Browser- oder Laufzeitumgebungsbeschränkungen, die Sie nicht akzeptieren können). Es ist im Grunde ein sehr einfach und saubere Klasse, die private Felder und eingeschränkte Accessoren verwendet, um das Verhalten einer Aufzählung zu simulieren. Dies mache ich manchmal in C#, wenn ich mehr Funktionalität in eine Aufzählung einbauen möchte.

Mir ist klar, dass private Klassenfelder zu diesem Zeitpunkt noch experimentell sind, aber es scheint zu funktionieren, um eine Klasse mit unveränderlichen Feldern/Eigenschaften zu erstellen. Die Browserunterstützung ist ebenfalls anständig. Die einzigen “großen” Browser, die es nicht unterstützen, sind Firefox (was sie sicher bald tun werden) und IE (wen interessiert das).

HAFTUNGSAUSSCHLUSS:
Ich bin kein Entwickler. Ich habe dies nur zusammengestellt, um die Einschränkungen nicht vorhandener Aufzählungen in JS zu lösen, als ich an einem persönlichen Projekt arbeitete.

Beispielklasse

class Colors {
    // Private Fields
    static #_RED = 0;
    static #_GREEN = 1;
    static #_BLUE = 2;

    // Accessors for "get" functions only (no "set" functions)
    static get RED() { return this.#_RED; }
    static get GREEN() { return this.#_GREEN; }
    static get BLUE() { return this.#_BLUE; }
}

Sie sollten Ihre Enums jetzt direkt aufrufen können.

Colors.RED; // 0
Colors.GREEN; // 1
Colors.BLUE; // 2

Die Kombination aus privaten Feldern und eingeschränkten Accessoren bedeutet, dass die vorhandenen Enum-Werte gut geschützt sind (sie sind jetzt im Wesentlichen Konstanten).

Colors.RED = 10 // Colors.RED is still 0
Colors._RED = 10 // Colors.RED is still 0
Colors.#_RED = 10 // Colors.RED is still 0

Prüfen wie TypeScript es macht. Grundsätzlich machen sie folgendes:

const MAP = {};

MAP[MAP[1] = 'A'] = 1;
MAP[MAP[2] = 'B'] = 2;

MAP['A'] // 1
MAP[1] // A

Verwenden Sie Symbole, frieren Sie Objekte ein, was immer Sie wollen.

  • Ich folge nicht, warum es verwendet wird MAP[MAP[1] = 'A'] = 1; Anstatt von MAP[1] = 'A'; MAP['A'] = 1;. Ich habe immer gehört, dass die Verwendung einer Zuweisung als Ausdruck schlechter Stil ist. Und welchen Nutzen ziehen Sie aus den gespiegelten Aufgaben?

    – Chiffr

    27. April 2018 um 17:05 Uhr

  • Hier ist ein Link dazu, wie die Enumerationszuordnung in es5 in ihren Dokumenten kompiliert wird. typescriptlang.org/docs/handbook/enums.html#reverse-mappings Ich kann mir vorstellen, dass es einfach einfacher und prägnanter wäre, es in eine einzelne Zeile zu kompilieren, z MAP[MAP[1] = 'A'] = 1;.

    – Umarmung geben

    2. Mai 2018 um 9:21 Uhr


  • Hm. Es sieht also so aus, als ob die Spiegelung es einfach macht, zwischen den Zeichenfolgen- und Zahlen- / Symboldarstellungen jedes Werts zu wechseln und diese Zeichenfolge oder Zahl / Symbol zu überprüfen x ist dabei ein gültiger Enum-Wert Enum[Enum[x]] === x. Es löst keines meiner ursprünglichen Probleme, könnte aber nützlich sein und macht nichts kaputt.

    – Chiffr

    2. Mai 2018 um 16:13 Uhr

  • Denken Sie daran, dass TypeScript eine Ebene der Robustheit hinzufügt, die verloren geht, sobald der TS-Code kompiliert ist. Wenn Ihre gesamte App in TS geschrieben ist, ist das großartig, aber wenn Sie möchten, dass JS-Code robust ist, klingt die eingefrorene Symbolkarte nach einem sichereren Muster.

    – Dominostein

    6. April 2020 um 7:08 Uhr


1293750cookie-checkEnums in Javascript mit ES6

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy