Aufzählungen in TypeScript haben vier verschiedene Aspekte, die Sie beachten müssen. Zunächst einige Definitionen:
"Suchobjekt"
Wenn Sie diese Aufzählung schreiben:
enum Foo { X, Y }
TypeScript gibt das folgende Objekt aus:
var Foo;
(function (Foo) {
Foo[Foo["X"] = 0] = "X";
Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));
Ich werde dies als Suchobjekt bezeichnen . Sein Zweck ist zweifach: als Abbildung von dienen Strings zu Zahlen , zum Beispiel beim Schreiben Foo.X
oder Foo['X']
, und als eine Abbildung von dienen Zahlen zu Strings . Diese umgekehrte Zuordnung ist nützlich für Debugging- oder Protokollierungszwecke - Sie haben häufig den Wert 0
oder 1
und möchten die entsprechende Zeichenfolge "X"
oder erhalten "Y"
.
"deklarieren" oder " umgebungs "
In TypeScript können Sie Dinge "deklarieren", über die der Compiler Bescheid wissen sollte, für die er jedoch keinen Code ausgibt. Dies ist nützlich, wenn Sie Bibliotheken wie jQuery haben, die ein Objekt definieren (z. B. $
), zu dem Sie Typinformationen wünschen, aber keinen vom Compiler erstellten Code benötigen. Die Spezifikation und andere Dokumentationen beziehen sich auf Erklärungen, die auf diese Weise abgegeben wurden und sich in einem "Umgebungs" -Kontext befinden. Es ist wichtig zu beachten, dass alle Deklarationen in einer .d.ts
Datei "ambient" sind ( declare
je nach Deklarationstyp entweder einen expliziten Modifikator erforderlich oder implizit).
"Inlining"
Aus Gründen der Leistung und der Codegröße ist es häufig vorzuziehen, beim Kompilieren einen Verweis auf ein Enum-Mitglied durch sein numerisches Äquivalent zu ersetzen:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
Die Spezifikation nennt diese Substitution , ich werde sie Inlining nennen, weil sie cooler klingt. Manchmal möchten Sie nicht, dass Enum-Mitglieder eingefügt werden, z. B. weil sich der Enum-Wert in einer zukünftigen Version der API möglicherweise ändert.
Enums, wie funktionieren sie?
Lassen Sie uns dies nach jedem Aspekt einer Aufzählung aufschlüsseln. Leider wird in jedem dieser vier Abschnitte auf Begriffe aus allen anderen Abschnitten verwiesen, sodass Sie diese ganze Sache wahrscheinlich mehr als einmal lesen müssen.
berechnet gegen nicht berechnet (konstant)
Enum-Mitglieder können entweder berechnet werden oder nicht. Die Spezifikation nennt nicht berechnete Mitglieder konstant , aber ich werde sie nicht berechnet nennen , um Verwechslungen mit const zu vermeiden .
Ein berechnetes Enum-Mitglied ist eines, dessen Wert zur Kompilierungszeit nicht bekannt ist. Verweise auf berechnete Mitglieder können natürlich nicht eingefügt werden. Umgekehrt ist ein nicht berechnetes Enum-Mitglied einmal vorhanden, dessen Wert zur Kompilierungszeit bekannt ist . Verweise auf nicht berechnete Mitglieder werden immer eingefügt.
Welche Enum-Mitglieder werden berechnet und welche nicht berechnet? Erstens sind alle Mitglieder einer const
Aufzählung konstant (dh nicht berechnet), wie der Name schon sagt. Bei einer nicht konstanten Aufzählung hängt es davon ab, ob Sie eine Umgebungsaufzählung (deklarieren) oder eine nicht umgebungsbezogene Aufzählung betrachten.
Ein Mitglied einer declare enum
(dh Umgebungsaufzählung) ist genau dann konstant, wenn es einen Initialisierer hat. Andernfalls wird es berechnet. Beachten Sie, dass in a declare enum
nur numerische Initialisierer zulässig sind. Beispiel:
declare enum Foo {
X, // Computed
Y = 2, // Non-computed
Z, // Computed! Not 3! Careful!
Q = 1 + 1 // Error
}
Schließlich gelten Mitglieder von nicht deklarierten Nicht-Konstanten-Aufzählungen immer als berechnet. Ihre initialisierenden Ausdrücke werden jedoch auf Konstanten reduziert, wenn sie zur Kompilierungszeit berechenbar sind. Dies bedeutet, dass nicht konstante Enum-Mitglieder niemals inline sind (dieses Verhalten wurde in TypeScript 1.5 geändert, siehe "Änderungen in TypeScript" unten).
const vs non-const
const
Eine Enum-Deklaration kann den const
Modifikator haben. Wenn eine Aufzählung vorhanden ist const
, werden alle Verweise auf ihre Mitglieder eingefügt.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
const enums erzeugen beim Kompilieren kein Lookup-Objekt. Aus diesem Grund ist es ein Fehler, Foo
im obigen Code zu referenzieren , außer als Teil einer Mitgliedsreferenz. Zur Foo
Laufzeit ist kein Objekt vorhanden.
non-const
Wenn eine Enum-Deklaration nicht über den const
Modifikator verfügt, werden Verweise auf ihre Mitglieder nur dann eingefügt, wenn das Mitglied nicht berechnet wurde. Eine nicht konstante, nicht deklarierte Aufzählung erzeugt ein Suchobjekt.
deklarieren (Umgebungs) vs nicht deklarieren
Ein wichtiges Vorwort ist, dass declare
TypeScript eine ganz bestimmte Bedeutung hat: Dieses Objekt existiert woanders . Es dient zur Beschreibung vorhandener Objekte. Das declare
Definieren von Objekten, die tatsächlich nicht existieren, kann schlimme Folgen haben. Wir werden diese später untersuchen.
erklären
A declare enum
gibt kein Suchobjekt aus. Verweise auf seine Mitglieder werden eingefügt, wenn diese Mitglieder berechnet werden (siehe oben zu berechnet oder nicht berechnet).
Es ist wichtig , dass andere Formen der Bezugnahme auf eine zu beachten declare enum
sind erlaubt, zum Beispiel ist dieser Code nicht ein Übersetzungsfehler , sondern wird zur Laufzeit fehlschlagen:
// Note: Assume no other file has actually created a Foo var at runtime
declare enum Foo { Bar }
var s = 'Bar';
var b = Foo[s]; // Fails
Dieser Fehler fällt unter die Kategorie "Lüg den Compiler nicht an". Wenn Sie Foo
zur Laufzeit kein Objekt mit dem Namen haben, schreiben Sie nicht declare enum Foo
!
A unterscheidet declare const enum
sich nicht von a const enum
, außer im Fall von --preserveConstEnums (siehe unten).
nicht deklarieren
Eine nicht deklarierte Aufzählung erzeugt ein Suchobjekt, wenn dies nicht der Fall ist const
. Inlining ist oben beschrieben.
--preserveConstEnums Flag
Dieses Flag hat genau einen Effekt: Nicht deklarierte Konstanten geben ein Suchobjekt aus. Inlining ist nicht betroffen. Dies ist nützlich zum Debuggen.
Häufige Fehler
Der häufigste Fehler ist die Verwendung eines, declare enum
wenn ein regulärer enum
oder const enum
angemessener wäre. Eine übliche Form ist folgende:
module MyModule {
// Claiming this enum exists with 'declare', but it doesn't...
export declare enum Lies {
Foo = 0,
Bar = 1
}
var x = Lies.Foo; // Depend on inlining
}
module SomeOtherCode {
// x ends up as 'undefined' at runtime
import x = MyModule.Lies;
// Try to use lookup object, which ought to exist
// runtime error, canot read property 0 of undefined
console.log(x[x.Foo]);
}
Denken Sie an die goldene Regel: Niemals declare
Dinge, die es eigentlich nicht gibt . Verwenden const enum
Sie diese Option, wenn Sie immer Inlining möchten oder enum
wenn Sie das Suchobjekt möchten.
Änderungen in TypeScript
Zwischen TypeScript 1.4 und 1.5 wurde das Verhalten geändert (siehe https://github.com/Microsoft/TypeScript/issues/2183 ), sodass alle Mitglieder von nicht deklarierten Nicht-Konstanten-Aufzählungen als berechnet behandelt werden, auch wenn Sie werden explizit mit einem Literal initialisiert. Dieses „Auftrennung aufzuheben , das Baby“, so zu sprechen, so dass das inlining Verhalten berechenbarer und sauberer das Konzept der Trennung const enum
von regelmäßigen enum
. Vor dieser Änderung wurden nicht berechnete Mitglieder von Nicht-Konstanten-Enums aggressiver eingefügt.