Closure Compiler prevede che l'input JavaScript sia conforme ad alcune limitazioni. Maggiore è il livello di ottimizzazione che chiedi al compilatore di eseguire, maggiori sono le restrizioni che il compilatore impone al JavaScript di input.
Questo documento descrive le principali limitazioni per ogni livello di ottimizzazione. Consulta anche questa pagina wiki per ulteriori ipotesi formulate dal compilatore.
Limitazioni per tutti i livelli di ottimizzazione
Il compilatore impone le seguenti due limitazioni a tutto il codice JavaScript che elabora, 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à del linguaggio ufficiali.
Le funzionalità specifiche del browser conformi alla specifica del linguaggio ECMAScript appropriata funzioneranno correttamente con il compilatore. Ad esempio, gli oggetti ActiveX vengono creati con la sintassi JavaScript legale, quindi il codice che crea oggetti ActiveX funziona con il compilatore.
I responsabili della manutenzione del compilatore lavorano attivamente per supportare le nuove versioni del linguaggio e le relative funzionalità. I progetti possono specificare la versione del linguaggio ECMAScript che intendono utilizzare tramite il flag
--language_in
.Il compilatore non conserva i commenti.
Tutti i livelli di ottimizzazione del compilatore rimuovono i commenti, pertanto il codice che si basa su commenti con formattazione speciale 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 racchiudendo i commenti condizionali in 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 delle funzioni, le variabili locali e le funzioni definite localmente per ridurre le dimensioni del codice. Tuttavia, alcune strutture JavaScript possono interrompere questo processo di ridenominazione.
Evita i seguenti costrutti e pratiche quando utilizzi SIMPLE_OPTIMIZATIONS
:
with
:Quando utilizzi
with
, il compilatore non può distinguere tra una variabile locale e una proprietà dell'oggetto con lo stesso nome, e quindi rinomina tutte le istanze del nome.Inoltre, l'istruzione
with
rende il codice più difficile da leggere per gli utenti. L'istruzionewith
modifica le normali regole per la risoluzione dei nomi e può rendere difficile anche per il programmatore che ha scritto il codice identificare a cosa si riferisce un nome.eval()
:Il compilatore non analizza l'argomento stringa di
eval()
, pertanto non rinominerà alcun simbolo all'interno di questo argomento.Rappresentazioni stringa dei nomi di funzioni o parametri:
Il compilatore rinomina le funzioni e i parametri delle funzioni, ma non modifica le stringhe del codice che fanno riferimento a funzioni o parametri per nome. Pertanto, evita di rappresentare i nomi di funzioni o dei parametri come stringhe nel codice. Ad esempio, la funzione della libreria Prototype
argumentNames()
utilizzaFunction.toString()
per recuperare i nomi dei parametri di una funzione. Tuttavia, anche seargumentNames()
potrebbe invogliarti a utilizzare i nomi degli argomenti nel 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 inutilizzato e l'appiattimento delle proprietà. Questi nuovi passaggi impongono
ulteriori restrizioni al JavaScript di input. In generale, l'utilizzo delle
funzionalità dinamiche di JavaScript impedisce l'analisi statica corretta del
codice.
Implicazioni della ridenominazione di variabili, funzioni e proprietà globali:
Il cambio di nome globale di ADVANCED_OPTIMIZATIONS
rende
pericolose le seguenti pratiche:
Riferimenti esterni non dichiarati:
Per rinominare correttamente variabili, funzioni e proprietà globali, il compilatore deve conoscere tutti i riferimenti a queste variabili globali. Devi comunicare al compilatore i simboli definiti al di fuori del codice in fase di compilazione. Compilazione avanzata ed extern 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. Advanced Compilation and Externs descrive 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 mai 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
Fare riferimento alle variabili come proprietà dell'oggetto globale:
Il compilatore rinomina le proprietà e le variabili in modo indipendente. Ad esempio, il compilatore tratta i due riferimenti seguenti a
foo
in modo diverso, anche se sono equivalenti:var foo = {}; window.foo; // BAD
Questo codice potrebbe 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 capisce che
initX()
einitY()
vengono chiamati nel ciclofor
, e quindi rimuove entrambi questi metodi.Tieni presente che se passi una funzione come parametro, il compilatore può trovare le chiamate a quel 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 dell'appiattimento delle proprietà degli oggetti
In modalità avanzata, il compilatore comprime le proprietà dell'oggetto per prepararsi all'abbreviazione dei nomi. Ad esempio, il compilatore trasforma questo codice:
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
in questo modo:
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
Questo appiattimento delle proprietà consente alla successiva passata di ridenominazione di rinominare in modo più efficiente. Il compilatore può sostituire foo$bar
con un
singolo carattere, ad esempio.
Tuttavia, l'appiattimento delle proprietà rende pericolose anche le seguenti pratiche:
Utilizzo di
this
al di fuori di costruttori e metodi prototipo:L'appiattimento delle proprietà può modificare 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
all'interno difoo.bar
si riferisce afoo
. Dopo la trasformazione,this
si riferisce althis
globale. In casi come questo, il compilatore genera questo avviso:"WARNING - dangerous use of this in static method foo.bar"
Per evitare che l'appiattimento delle proprietà interrompa i riferimenti a
this
, utilizzathis
solo all'interno di costruttori e metodi prototipo. Il significato dithis
è univoco quando chiami un costruttore con la parola chiavenew
o all'interno di una funzione che è una proprietà di unprototype
.Utilizzo di metodi statici senza sapere su quale classe vengono chiamati:
Ad esempio, se hai:
il compilatore comprimerà entrambi i metodiclass A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
create
(dopo la transpilazione da ES6 a ES5), quindi la chiamatacls.create()
non andrà 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(); } }
Utilizzo di super in un metodo statico senza conoscere la superclasse:
Il seguente codice è sicuro perché il compilatore sa che
super.sayHi()
si riferisce aParent.sayHi()
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends Parent { static sayHi() { super.sayHi(); } } Child.sayHi();
Tuttavia, l'appiattimento della proprietà interromperà il 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 Object.defineProperties o getter/setter ES6:
Il compilatore non comprende bene queste costruzioni. I getter e i setter ES6 vengono trasformati in Object.defineProperties(...) tramite la transpilazione. Al momento, il compilatore non è in grado di analizzare staticamente questa struttura e presuppone che l'accesso e l'impostazione delle proprietà non abbiano effetti collaterali. Ciò può avere ripercussioni pericolose. Ad esempio:
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Viene 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; }}});
È stato stabilito che C.someProperty non ha effetti collaterali, pertanto è stata rimossa.