Il compilatore Closure prevede che il suo input JavaScript sia conforme a poche limitazioni. Più alto è il livello di ottimizzazione chiesto al compilatore, maggiore è il numero di limitazioni che il componente inserisce nel codice JavaScript di input.
Questo documento descrive le restrizioni principali per ogni livello di ottimizzazione. Consulta anche questa pagina wiki per ulteriori ipotesi fatte dal compilatore.
Limitazioni per tutti i livelli di ottimizzazione
Il compilatore applica le due limitazioni seguenti per tutti i livelli JavaScript elaborati, per tutti i livelli di ottimizzazione:
Il compilatore riconosce solo ECMAScript.
ECMAScript 5 è la versione di JavaScript supportata quasi ovunque. Tuttavia, il compilatore supporta anche molte delle funzionalità di ECMAScript 6. Il compilatore supporta solo le funzionalità relative alla lingua ufficiale.
Le funzionalità specifiche del browser che sono conformi alla specifica del linguaggio ECMAScript appropriata si adattano al compilatore. Ad esempio, gli oggetti ActiveX vengono creati con una sintassi JavaScript legale, quindi il codice che crea oggetti ActiveX funziona con il compilatore.
I gestori di compilazione lavorano attivamente per supportare le nuove versioni delle lingue e le loro funzionalità. I progetti possono specificare la versione del linguaggio ECMAScript che preferiscono mediante il flag
--language_in
.Il compilatore non conserva i commenti.
Tutti i livelli di ottimizzazione del compilatore rimuovono i commenti, quindi il codice che si basa sui commenti appositamente formattati non funziona con il compilatore.
Ad esempio, poiché il compilatore non conserva i commenti, non puoi utilizzare direttamente i "commenti condizionali" di JScript. Tuttavia, puoi aggirare questa limitazione inserendo i commenti condizionali nelle espressioni
eval()
. Il compilatore può elaborare il seguente codice senza generare un errore:x = eval("/*@cc_on 2+@*/ 0");
Nota: puoi includere licenze open source e altro testo importante nella parte superiore dell'output del compilatore utilizzando l'annotazione @preserve.
Limitazioni per SIMPLE_OPTIMIZATIONS
Il livello di ottimizzazione Semplice rinomina i parametri della funzione, le variabili locali e le funzioni definite localmente per ridurre le dimensioni del codice. Tuttavia, alcuni costrutti JavaScript possono interrompere questo processo di ridenominazione.
Evita i seguenti costrutti e pratiche quando utilizzi SIMPLE_OPTIMIZATIONS
:
with
:Quando utilizzi
with
, il compilatore non è in grado di distinguere tra una variabile locale e una proprietà dell'oggetto con lo stesso nome, quindi rinomina tutte le istanze del nome.Inoltre, l'istruzione
with
rende più difficile la lettura del codice. L'istruzionewith
modifica le normali regole per la risoluzione dei nomi e può rendere difficile per il programmatore che ha scritto il codice identificare il nome di un nome.eval()
:Il compilatore non analizza l'argomento stringa di
eval()
, quindi non rinomina alcun simbolo all'interno di questo argomento.Rappresentazioni di stringhe di nomi di funzioni o parametri:
Il compilatore rinomina le funzioni e i parametri delle funzioni, ma non modifica le stringhe nel codice che fanno riferimento a funzioni o parametri in base al nome. Dovresti quindi evitare di rappresentare i nomi di funzioni o parametri come stringhe nel codice. Ad esempio, la funzione della libreria di prototipo
argumentNames()
utilizzaFunction.toString()
per recuperare i nomi dei parametri di una funzione. Tuttavia, anche seargumentNames()
potrebbe tentare di utilizzare i nomi degli argomenti nel tuo codice, la compilazione in modalità semplice interrompe questo tipo di riferimento.
Limitazioni per ADVANCED_OPTIMIZATIONS
Il livello di compilazione ADVANCED_OPTIMIZATIONS
esegue le stesse trasformazioni di SIMPLE_OPTIMIZATIONS
e aggiunge anche la ridenominazione globale di proprietà, variabili e funzioni, l'eliminazione del codice non valido e la suddivisione delle proprietà. Questi nuovi pass comportano restrizioni aggiuntive sul codice JavaScript di input. In generale, l'utilizzo delle funzionalità dinamiche di JavaScript impedisce l'analisi statica corretta del codice.
Implicazioni relative alla ridenominazione di variabili, funzioni e proprietà globali:
La ridenominazione globale di ADVANCED_OPTIMIZATIONS
rende pericolose le seguenti pratiche:
Riferimenti esterni non dichiarati:
Per rinominare correttamente le variabili, le funzioni e le proprietà globali, il compilatore deve conoscere tutti i riferimenti a tali elementi globali. Devi comunicare al compilatore i simboli definiti al di fuori del codice che viene compilato. Compilation avanzata ed esterne descrive come dichiarare i simboli esterni.
Utilizzo di nomi interni non esportati nel codice esterno:
Il codice compilato deve esportare tutti i simboli a cui fa riferimento il codice non compilato. Compilation avanzata ed esterne descrivono come esportare i simboli.
Utilizzo dei nomi delle stringhe per fare riferimento alle proprietà degli oggetti:
Il compilatore rinomina le proprietà in modalità avanzata, ma non rinomina le stringhe.
var x = { renamed_property: 1 }; var y = x.renamed_property; // This is OK. // 'renamed_property' below doesn't exist on x after renaming, so the // following evaluates to false. if ( 'renamed_property' in x ) {}; // BAD // The following also fails: x['renamed_property']; // BAD
Se devi fare riferimento a una proprietà con una stringa tra virgolette, utilizza sempre una stringa tra virgolette:
var x = { 'unrenamed_property': 1 }; x['unrenamed_property']; // This is OK. if ( 'unrenamed_property' in x ) {}; // This is OK
Riferimento alle variabili come proprietà dell'oggetto globale:
Il compilatore rinomina proprietà e variabili in modo indipendente. Ad esempio, il compilatore tratta i due riferimenti a
foo
in modo diverso, anche se sono equivalenti:var foo = {}; window.foo; // BAD
Il codice può essere compilato in:
var a = {}; window.b;
Se devi fare riferimento a una variabile come proprietà dell'oggetto globale, fai sempre riferimento in questo modo:
window.foo = {} window.foo;
Implications of dead code elimination
The ADVANCED_OPTIMIZATIONS
compilation level removes code
that is never executed. This elimination of dead code makes the
following practices dangerous:
Calling functions from outside of compiled code:
When you compile functions without compiling the code that calls those functions, the Compiler assumes that the functions are never called and removes them. To avoid unwanted code removal, either:
- compile all the JavaScript for your application together, or
- export compiled functions.
Advanced Compilation and Externs describes both of these approaches in greater detail.
Retrieving functions through iteration over constructor or prototype properties:
To determine whether a function is dead code, the Compiler has to find all the calls to that function. By iterating over the properties of a constructor or its prototype you can find and call methods, but the Compiler can't identify the specific functions called in this manner.
For example, the following code causes unintended code removal:
function Coordinate() { } Coordinate.prototype.initX = function() { this.x = 0; } Coordinate.prototype.initY = function() { this.y = 0; } var coord = new Coordinate(); for (method in Coordinate.prototype) { Coordinate.prototype[method].call(coord); // BAD }
Il compilatore non comprende che
initX()
einitY()
vengono chiamati nel loopfor
, pertanto rimuove entrambi i metodi.Tieni presente che se trasmetti una funzione come parametro, il compilatore può trovare le chiamate al parametro. Ad esempio, il compilatore non rimuove la funzione
getHello()
quando compila il seguente codice in modalità avanzata.function alertF(f) { alert(f()); } function getHello() { return 'hello'; } // The Compiler figures out that this call to alertF also calls getHello(). alertF(getHello); // This is OK.
Implicazioni della suddivisione delle proprietà degli oggetti
In modalità avanzata, il compilatore comprime le proprietà degli oggetti per prepararsi all'accorciamento del nome. Ad esempio, il compilatore trasforma questo aspetto:
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
in questo:
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
La suddivisione delle proprietà consente al pass di ridenominazione successivo di rinominare in modo più efficiente. Ad esempio, il compilatore può sostituire foo$bar
con un singolo carattere.
Tuttavia, la suddivisione delle proprietà rende pericolose le seguenti pratiche:
Utilizzo di
this
al di fuori dei costruttori e dei metodi dei prototipi:La suddivisione delle proprietà può cambiare il significato della parola chiave
this
all'interno di una funzione. Ad esempio:var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD foo.bar("hello");
diventa:
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Prima della trasformazione,
this
infoo.bar
si riferisce afoo
. Dopo la trasformazione,this
si riferisce allathis
globale. In casi come questo, il compilatore genera questo avviso:"WARNING - dangerous use of this in static method foo.bar"
Per evitare che la suddivisione della proprietà spezzi i riferimenti a
this
, utilizzathis
solo all'interno dei costruttori e dei metodi prototipo. Il significato dithis
è inequivocabile quando si chiama un costruttore con la parola chiavenew
o all'interno di una funzione che è una proprietà diprototype
.Utilizzare metodi statici senza sapere a quale classe fanno riferimento:
Ad esempio, se hai:
class A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
Il compilatore comprimerà entrambi i metodicreate
(dopo il trasferimento da ES6 a ES5), in modo che la chiamatacls.create()
non vada a buon fine. Puoi evitarlo con l'annotazione@nocollapse
:class A { /** @nocollapse */ static create() { return new A(); } } class B { /** @nocollapse */ static create() { return new A(); } }
Utilizzare la super metodo in modo statico senza conoscere la superclasse:
Il seguente codice è sicuro perché il compilatore sa che
super.sayHi()
fa riferimento aParent.sayHi()
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends Parent { static sayHi() { super.sayHi(); } } Child.sayHi();
Tuttavia, la suddivisione delle proprietà comporterà l'interruzione del seguente codice, anche se
myMixin(Parent).sayHi
è uguale aParent.sayHi
non compilato:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends myMixin(Parent) { static sayHi() { super.sayHi(); } } Child.sayHi();
Evita questa interruzione con l'annotazione
/** @nocollapse */
.Utilizzo di un oggetto get./setter ES6 o:
Il compilatore non comprende bene questi costrutti. Il getter e i setter ES6 vengono trasformati in Object.defineProperties(...) tramite la traspirazione. Al momento, il compilatore non è in grado di analizzare in modo statico questo costrutto e presuppone che l'accesso e l'impostazione delle proprietà siano privi di effetti collaterali. Questo può avere ripercussioni pericolose. Ad esempio:
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Compilato in:
C = function() {}; Object.defineProperties(C, {a: // Note someProperty is also renamed to 'a'. {configurable:!0, enumerable:!0, get:function() { console.log("hello world"); return 1; }}});
È stata determinata la mancanza di effetti collaterali per C.someProperty, pertanto è stata rimossa.