Ci sono quattro diversi aspetti da enumerare in TypeScript di cui devi essere a conoscenza. Innanzitutto, alcune definizioni:
"oggetto di ricerca"
Se scrivi questa enumerazione:
enum Foo { X, Y }
TypeScript emetterà il seguente oggetto:
var Foo;
(function (Foo) {
Foo[Foo["X"] = 0] = "X";
Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));
Mi riferirò a questo come l'oggetto di ricerca . Il suo scopo è duplice: servire da mappatura da stringhe a numeri , ad esempio quando si scrive Foo.X
o Foo['X']
, e da mappare da numeri a stringhe . Questa mappatura inversa è utile per scopi di debug o registrazione: spesso avrai il valore 0
o 1
e vorrai ottenere la stringa corrispondente "X"
o "Y"
.
"declare" o " ambient "
In TypeScript, puoi "dichiarare" cose che il compilatore dovrebbe conoscere, ma non emettere effettivamente codice per. Ciò è utile quando si dispone di librerie come jQuery che definiscono un oggetto (ad esempio $
) di cui si desiderano informazioni sul tipo, ma non è necessario alcun codice creato dal compilatore. Le specifiche e altra documentazione si riferiscono a dichiarazioni fatte in questo modo come se fossero in un contesto "ambientale"; è importante notare che tutte le dichiarazioni in un .d.ts
file sono "ambientali" (o richiedono un declare
modificatore esplicito o lo hanno implicitamente, a seconda del tipo di dichiarazione).
"Inlining"
Per motivi di prestazioni e dimensione del codice, è spesso preferibile avere un riferimento a un membro enum sostituito dal suo equivalente numerico quando viene compilato:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
La specifica chiama questa sostituzione , la chiamerò inlining perché suona più interessante. A volte lo farai non desidera che i membri enum da inline, ad esempio perché il valore enum potrebbe cambiare in una futura versione di API.
Enum, come funzionano?
Analizziamolo per ogni aspetto di un enum. Sfortunatamente, ciascuna di queste quattro sezioni farà riferimento a termini di tutte le altre, quindi probabilmente dovrai leggere l'intera cosa più di una volta.
calcolato vs non calcolato (costante)
I membri di enum possono essere calcolati o meno. La specifica chiama costanti membri non calcolati , ma li chiamerò non calcolati per evitare confusione con const .
Un membro enum calcolato è un membro il cui valore non è noto in fase di compilazione. I riferimenti ai membri calcolati non possono essere inseriti in linea, ovviamente. Al contrario, un membro enum non calcolato è una volta il cui valore è noto in fase di compilazione. I riferimenti a membri non calcolati sono sempre inline.
Quali membri di enum vengono calcolati e quali non vengono calcolati? Primo, tutti i membri di un const
enum sono costanti (cioè non calcolati), come suggerisce il nome. Per un'enumerazione non const, dipende dal fatto che si stia osservando un'enumerazione ambientale (dichiarare) o un'enumerazione non ambientale.
Un membro di a declare enum
(es. Enum ambientale) è costante se e solo se ha un inizializzatore. Altrimenti, viene calcolato. Notare che in a declare enum
, sono consentiti solo inizializzatori numerici. Esempio:
declare enum Foo {
X, // Computed
Y = 2, // Non-computed
Z, // Computed! Not 3! Careful!
Q = 1 + 1 // Error
}
Infine, i membri di enumerazioni non dichiarate non cost vengono sempre considerati calcolati. Tuttavia, le loro espressioni di inizializzazione sono ridotte a costanti se sono calcolabili in fase di compilazione. Ciò significa che i membri non-const enum non sono mai inline (questo comportamento è cambiato in TypeScript 1.5, vedi "Modifiche in TypeScript" in fondo)
const vs non const
const
Una dichiarazione enum può avere il const
modificatore. Se un'enumerazione è const
, tutti i riferimenti ai suoi membri sono inline.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
const enums non producono un oggetto di ricerca quando vengono compilati. Per questo motivo, è un errore fare riferimento Foo
nel codice precedente tranne come parte di un riferimento a un membro. Nessun Foo
oggetto sarà presente in fase di esecuzione.
non-const
Se una dichiarazione enum non ha il const
modificatore, i riferimenti ai suoi membri sono inline solo se il membro non è calcolato. Un enum non-const, non-declare produrrà un oggetto di ricerca.
declare (ambient) vs non-declare
Una premessa importante è che declare
in TypeScript ha un significato molto specifico: questo oggetto esiste da qualche altra parte . Serve per descrivere oggetti esistenti . Usare declare
per definire oggetti che in realtà non esistono può avere conseguenze negative; li esploreremo più tardi.
dichiarare
A declare enum
non emetterà un oggetto di ricerca. I riferimenti ai suoi membri sono inline se questi membri sono calcolati (vedi sopra su calcolati vs non calcolati).
E 'importante notare che altre forme di riferimento ad una declare enum
sono permessi, ad esempio, questo codice è non è un errore di compilazione, ma sarà fallire in fase di esecuzione:
// 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
Questo errore rientra nella categoria "Non mentire al compilatore". Se non hai un oggetto denominato Foo
in fase di esecuzione, non scrivere declare enum Foo
!
A declare const enum
non è diverso da a const enum
, tranne nel caso di --preserveConstEnums (vedi sotto).
non dichiarare
Se non lo è, un'enumerazione non dichiarata produce un oggetto di ricerca const
. L'inlining è descritto sopra.
--preserveConstEnums flag
Questo flag ha esattamente un effetto: le enumerazioni const non dichiarate genereranno un oggetto di ricerca. L'inlining non è interessato. Questo è utile per il debug.
Errori comuni
L'errore più comune è usare un declare enum
quando un normale enum
o const enum
sarebbe più appropriato. Una forma comune è questa:
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]);
}
Ricorda la regola d'oro: mai declare
cose che in realtà non esistono . Usalo const enum
se vuoi sempre inlining o enum
se vuoi l'oggetto di ricerca.
Modifiche in TypeScript
Tra TypeScript 1.4 e 1.5, è stata apportata una modifica al comportamento (vedere https://github.com/Microsoft/TypeScript/issues/2183 ) per fare in modo che tutti i membri di enumerazioni non dichiarate non cost vengano trattati come calcolati, anche se sono esplicitamente inizializzati con un letterale. Questo "separare il bambino", per così dire, rende il comportamento in linea più prevedibile e separa in modo più netto il concetto di const enum
regolare enum
. Prima di questa modifica, i membri non calcolati di enumerazioni non cost venivano inseriti in modo più aggressivo.