Informazioni sulle limitazioni imposte da Closure Compiler

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

  • Utilizzo di metodi statici senza sapere su quale classe vengono chiamati:

    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 la transpilazione da ES6 a ES5), quindi la chiamata cls.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 a Parent.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 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 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.