Informazioni sulle limitazioni imposte dal compilatore della chiusura

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'istruzione with 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() utilizza Function.toString() per recuperare i nomi dei parametri di una funzione. Tuttavia, anche se argumentNames() 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() e initY() vengono chiamati nel loop for, 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 in foo.bar si riferisce a foo. Dopo la trasformazione, this si riferisce alla this 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, utilizza this solo all'interno dei costruttori e dei metodi prototipo. Il significato di this è inequivocabile quando si chiama un costruttore con la parola chiave new o all'interno di una funzione che è una proprietà di prototype.

  • 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 metodi create (dopo il trasferimento da ES6 a ES5), in modo che la chiamata cls.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 a Parent.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 a Parent.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.