Informationen zu den vom Compiler auferlegten Einschränkungen

Der Closure Compiler erwartet, dass seine JavaScript-Eingabe einigen Einschränkungen entspricht. Je höher der Optimierungsgrad, den Sie den Compiler ausführen sollen, desto mehr Einschränkungen gelten für den Eingabe-JavaScript-Code.

In diesem Dokument werden die wichtigsten Einschränkungen für die einzelnen Optimierungsebenen beschrieben. Weitere Annahmen des Compilers finden Sie auf dieser Wiki-Seite.

Einschränkungen für alle Optimierungsstufen

Der Compiler wendet die folgenden beiden Einschränkungen für den von ihm verarbeiteten JavaScript-Code auf allen Optimierungsstufen an:

  • Der Compiler erkennt nur ECMAScript.

    ECMAScript 5 ist eine fast überall unterstützte JavaScript-Version. Der Compiler unterstützt jedoch auch viele Funktionen von ECMAScript 6. Der Compiler unterstützt nur offizielle Sprachfunktionen.

    Browserspezifische Funktionen, die der entsprechenden ECMAScript-Sprachspezifikation entsprechen, funktionieren mit dem Compiler. ActiveX-Objekte werden beispielsweise mit der JavaScript-Syntax erstellt, sodass der Code, der ActiveX-Objekte erstellt, mit dem Compiler funktioniert.

    Die Compiler-Worker arbeiten aktiv daran, neue Sprachversionen und ihre Features zu unterstützen. Projekte können mit dem Flag --language_in die gewünschte ECMAScript-Sprachversion angeben.

  • Der Compiler speichert keine Kommentare.

    Bei allen Compiler-Optimierungsebenen werden Kommentare entfernt. Code, der auf speziell formatierten Kommentaren basiert, funktioniert daher nicht mit dem Compiler.

    Da der Compiler beispielsweise keine Kommentare beibehält, können Sie die bedingten Kommentare von JScript nicht direkt verwenden. Sie können diese Einschränkung jedoch umgehen, indem Sie bedingte Kommentare in eval()-Ausdrücke einbinden. Der Compiler kann den folgenden Code verarbeiten, ohne einen Fehler zu generieren:

     x = eval("/*@cc_on 2+@*/ 0");
    

    Hinweis: Sie können Open-Source-Lizenzen und anderen wichtigen Text oben in der Compiler-Ausgabe mithilfe der Annotation @preserve einfügen.

Einschränkungen für SIMPLE_OPTIMIZATIONS

Bei der einfachen Optimierungsstufe werden Funktionsparameter, lokale Variablen und lokal definierte Funktionen umbenannt, um die Codegröße zu reduzieren. Einige JavaScript-Konstrukte können diese Umbenennung jedoch unterbrechen.

Vermeiden Sie die folgenden Konstrukte und Praktiken, wenn Sie SIMPLE_OPTIMIZATIONS verwenden:

  • with:

    Wenn Sie with verwenden, kann der Compiler nicht zwischen einer lokalen Variable und einem Objektattribut desselben Namens unterscheiden. Daher werden alle Instanzen des Namens umbenannt.

    Außerdem wird der Code durch die with-Anweisung schwerer lesbar. Die with-Anweisung ändert die normalen Regeln für die Namensauflösung und erschwert es dem Programmierer, den Code zu identifizieren.

  • eval():

    Der Compiler parst das Stringargument von eval() nicht und benennt daher keine Symbole in diesem Argument um.

  • Stringdarstellungen von Funktions- oder Parameternamen:

    Der Compiler benennt Funktionen und Funktionsparameter um, ändert jedoch keine Strings in Ihrem Code, die namentlich auf Funktionen oder Parameter verweisen. Daher sollten Sie Funktions- oder Parameternamen nicht als Strings in Ihrem Code darstellen. Die Funktion „Prototyp-Bibliothek“ argumentNames() verwendet beispielsweise Function.toString(), um die Namen der Parameter einer Funktion abzurufen. Wenn Sie jedoch versuchen, die Namen von Argumenten in Ihrem Code mit argumentNames() zu verwenden, wird diese Art der Referenz durch die Kompilierung im einfachen Modus unterbrochen.

Einschränkungen für ADVANCED_OPTIMIZATIONS

Die Kompilierungsebene ADVANCED_OPTIMIZATIONS führt dieselben Transformationen wie SIMPLE_OPTIMIZATIONS aus und fügt außerdem eine globale Umbenennung von Attributen, Variablen und Funktionen, die Eliminierung von Dead-Code und die Aufschlüsselung von Attributen hinzu. Durch diese neuen Karten/Tickets gelten zusätzliche Einschränkungen für das eingegebene JavaScript. Im Allgemeinen verhindert die Verwendung der dynamischen Funktionen von JavaScript die korrekte statische Analyse Ihres Codes.

Auswirkungen der Umbenennung globaler Variablen, Funktionen und Attribute:

Die globale Umbenennung von ADVANCED_OPTIMIZATIONS macht die folgenden Praktiken gefährlich:

  • Nicht angegebene externe Verweise:

    Um globale Variablen, Funktionen und Attribute korrekt umzubenennen, muss der Compiler alle Verweise auf diese globalen Elemente kennen. Sie müssen den Compiler über Symbole informieren, die außerhalb des kompilierten Codes definiert sind. Unter Erweiterte Kompilierung und externe Verbindungen wird beschrieben, wie externe Symbole deklariert werden.

  • Nicht exportierte interne Namen in externem Code verwenden:

    Kompilierter Code muss alle Symbole exportieren, auf die sich unkompilierter Code bezieht. Unter Erweiterte Kompilierung und Externs wird beschrieben, wie Symbole exportiert werden.

  • Stringnamen verwenden, um auf Objektattribute zu verweisen:

    Der Compiler benennt Attribute im erweiterten Modus um, jedoch keine Strings.

      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
    

    Wenn Sie auf ein Attribut mit String in Anführungszeichen verweisen möchten, verwenden Sie immer einen String in Anführungszeichen:

      var x = { 'unrenamed_property': 1 };
      x['unrenamed_property'];  // This is OK.
      if ( 'unrenamed_property' in x ) {};   // This is OK
    
  • Verweisen auf Variablen als Attribute des globalen Objekts:

    Der Compiler benennt Attribute und Variablen unabhängig um. Beispielsweise behandelt der Compiler die folgenden zwei Verweise auf foo unterschiedlich, obwohl sie gleichwertig sind:

      var foo = {};
      window.foo; // BAD
    

    Dieser Code kann unter anderem zu folgendem Code kompilieren:

      var a = {};
      window.b;
    

    Wenn Sie auf eine Variable als Attribut des globalen Objekts verweisen müssen, verweisen Sie immer auf diese Weise:

    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
    }
        

    Der Compiler versteht nicht, dass initX() und initY() in der for-Schleife aufgerufen werden, und entfernt daher beide Methoden.

    Wenn Sie eine Funktion als Parameter übergeben, kann der Compiler Aufrufe an diesen Parameter finden. Beispielsweise entfernt der Compiler die Funktion getHello() beim Kompilieren des folgenden Codes im erweiterten Modus nicht.

    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.
        

Auswirkungen der Aufschlüsselung von Objekteigenschaften

Im erweiterten Modus minimiert Compiler Objektattribute, um die Namenskürzung vorzubereiten. Der Compiler transformiert beispielsweise Folgendes:

   var foo = {};
   foo.bar = function (a) { alert(a) };
   foo.bar("hello");

in:

   var foo$bar = function (a) { alert(a) };
   foo$bar("hello");

Mit dieser Property-Vereinfachung kann die spätere Umbenennung effizienter umbenannt werden. Der Compiler kann beispielsweise foo$bar durch ein einzelnes Zeichen ersetzen.

Die Aufschlüsselung von Immobilien macht außerdem die folgenden Praktiken gefährlich:

  • this außerhalb von Konstruktoren und Prototypmethoden verwenden:

    Die Vereinfachung von Attributen kann die Bedeutung des Keywords this innerhalb einer Funktion ändern. Beispiel:

       var foo = {};
       foo.bar = function (a) { this.bad = a; }; // BAD
       foo.bar("hello");
    

    wird zu:

       var foo$bar = function (a) { this.bad = a; };
       foo$bar("hello");
    

    Vor der Transformation bezieht sich this in foo.bar auf foo. Nach der Transformation bezieht sich this auf die globale this. In diesem Fall gibt der Compiler folgende Warnung aus:

    "WARNING - dangerous use of this in static method foo.bar"

    Um eine Aufschlüsselung von Attributen in this zu vermeiden, verwenden Sie this nur in Konstruktoren und Prototypmethoden. Die Bedeutung von this ist eindeutig, wenn Sie einen Konstruktor mit dem Schlüsselwort new oder innerhalb einer Funktion aufrufen, die ein Attribut eines prototype ist.

  • Statische Methoden verwenden, ohne zu wissen, in welcher Klasse sie aufgerufen werden:

    Beispiel:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    
    Der Compiler minimiert beide create-Methoden (nach der Umwandlung von ES6 zu ES5), sodass der cls.create()-Aufruf fehlschlägt. Das lässt sich mit der Annotation @nocollapse vermeiden:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    

  • Super in einer statischen Methode verwenden, ohne die übergeordnete Klasse zu kennen:

    Der folgende Code ist sicher, da der Compiler weiß, dass sich super.sayHi() auf Parent.sayHi() bezieht:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends Parent {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    Bei der Aufschlüsselung von Attributen wird der folgende Code jedoch ungültig, selbst wenn myMixin(Parent).sayHi gleich Parent.sayHi-kompiliert ist:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends myMixin(Parent) {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    Vermeiden Sie diesen Fehler mit der Annotation /** @nocollapse */.

  • Mit „define.defineProperties“ oder Getes/Setters von ES6:

    Der Compiler kann diese Konstrukte nicht gut verstehen. GET-Getter und -Setter von ES6 werden durch Umwandlung in Object.defineProperties(...) transformiert. Derzeit kann der Compiler dieses Konstrukt nicht statisch analysieren. Es wird davon ausgegangen, dass der Zugriff auf und die Festlegung von Properties unbemerkt sind. Das kann gefährliche Folgen haben. Beispiel:

    class C {
      static get someProperty() {
        console.log("hello getters!");
      }
    }
    var unused = C.someProperty;
    

    Kompiliert nach:

    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;
    }}});
    

    Da C.someProperty keine Nebeneffekte hat, wurde es entfernt.