Einschränkungen des Closure Compilers

Der Closure Compiler erwartet, dass die JavaScript-Eingabe einigen Einschränkungen entspricht. Je höher der Optimierungsgrad, den Sie vom Compiler verlangen, desto mehr Einschränkungen werden für das eingegebene JavaScript festgelegt.

In diesem Dokument werden die wichtigsten Einschränkungen für die einzelnen Optimierungsstufen beschrieben. Weitere Annahmen des Compilers

Einschränkungen für alle Optimierungsstufen

Der Compiler unterliegt bei allen Optimierungsstufen den folgenden beiden Einschränkungen für das gesamte verarbeitete JavaScript:

  • Der Compiler erkennt nur ECMAScript.

    ECMAScript 5 ist die Version von JavaScript, die fast überall unterstützt wird. Der Compiler unterstützt jedoch auch viele der Funktionen in ECMAScript 6. Der Compiler unterstützt nur offizielle Sprachfunktionen.

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

    Die Compiler-Maintainer arbeiten aktiv daran, neue Sprachversionen und ihre Funktionen zu unterstützen. Projekte können mit dem Flag --language_in angeben, welche ECMAScript-Sprachversion sie verwenden möchten.

  • Der Compiler behält keine Kommentare bei.

    Bei allen Compiler-Optimierungsstufen werden Kommentare entfernt. Code, der auf speziell formatierte Kommentare angewiesen ist, 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 einfügen. Der Compiler kann den folgenden Code ohne Fehler verarbeiten:

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

    Hinweis:Sie können Open-Source-Lizenzen und andere wichtige Texte oben in die Compiler-Ausgabe einfügen, indem Sie die Annotation „@preserve“ verwenden.

Einschränkungen für SIMPLE_OPTIMIZATIONS

Bei der einfachen Optimierungsstufe werden Funktionsparameter, lokale Variablen und lokal definierte Funktionen umbenannt, um die Codegröße zu verringern. Einige JavaScript-Konstrukte können diesen Umbenennungsprozess 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 Variablen und einer Objekteigenschaft mit demselben Namen unterscheiden. Daher werden alle Instanzen des Namens umbenannt.

    Außerdem erschwert die with-Anweisung das Lesen Ihres Codes für Menschen. Die with-Anweisung ändert die normalen Regeln für die Namensauflösung und kann es selbst für den Programmierer, der den Code geschrieben hat, schwierig machen, zu erkennen, worauf sich ein Name bezieht.

  • eval():

    Der Compiler parst das String-Argument 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 aber keine Strings in Ihrem Code, die sich auf Funktionen oder Parameter beziehen. Sie sollten daher Funktions- oder Parameternamen nicht als Strings in Ihrem Code darstellen. Die Prototype-Bibliotheksfunktion argumentNames() verwendet beispielsweise Function.toString(), um die Namen der Parameter einer Funktion abzurufen. argumentNames() verleitet Sie vielleicht dazu, die Namen von Argumenten in Ihrem Code zu verwenden. Die Kompilierung im einfachen Modus macht diese Art von Referenz jedoch zunichte.

Einschränkungen für ADVANCED_OPTIMIZATIONS

Bei der Kompilierungsstufe ADVANCED_OPTIMIZATIONS werden dieselben Transformationen wie bei SIMPLE_OPTIMIZATIONS ausgeführt. Außerdem werden Eigenschaften, Variablen und Funktionen global umbenannt, nicht erreichbarer Code entfernt und Eigenschaften zusammengefasst. Diese neuen Durchläufe unterliegen zusätzlichen Einschränkungen für das eingegebene JavaScript. Im Allgemeinen verhindert die Verwendung der dynamischen Funktionen von JavaScript eine korrekte statische Analyse Ihres Codes.

Auswirkungen der Umbenennung globaler Variablen, Funktionen und Eigenschaften:

Durch die globale Umbenennung von ADVANCED_OPTIMIZATIONS werden die folgenden Praktiken gefährlich:

  • Nicht deklarierte externe Verweise:

    Damit globale Variablen, Funktionen und Attribute richtig umbenannt werden können, muss der Compiler alle Verweise auf diese globalen Elemente kennen. Sie müssen dem Compiler Symbole mitteilen, die außerhalb des zu kompilierenden Codes definiert sind. Unter Advanced Compilation and Externs wird beschrieben, wie externe Symbole deklariert werden.

  • Nicht exportierte interne Namen in externem Code verwenden:

    Im kompilierten Code müssen alle Symbole exportiert werden, auf die sich der nicht kompilierte Code bezieht. Unter Erweiterte Kompilierung und Externs wird beschrieben, wie Sie Symbole exportieren.

  • String-Namen verwenden, um auf Objekteigenschaften zu verweisen:

    Der Compiler benennt Eigenschaften im erweiterten Modus um, aber niemals 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 eine Property mit einem in Anführungszeichen gesetzten String verweisen müssen, verwenden Sie immer einen in Anführungszeichen gesetzten String:

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

    Der Compiler benennt Eigenschaften und Variablen unabhängig voneinander um. Der Compiler behandelt die folgenden beiden Referenzen auf foo beispielsweise unterschiedlich, obwohl sie gleichwertig sind:

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

    Dieser Code wird möglicherweise in Folgendes kompiliert:

      var a = {};
      window.b;

    Wenn Sie auf eine Variable als Attribut des globalen Objekts verweisen müssen, müssen Sie das immer so tun:

    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 erkennt nicht, dass initX() und initY() im for-Loop aufgerufen werden, und entfernt daher beide Methoden.

    Wenn Sie eine Funktion als Parameter übergeben, kann der Compiler Aufrufe dieses Parameters finden. Der Compiler entfernt beispielsweise die Funktion getHello() nicht, wenn er den folgenden Code im erweiterten Modus kompiliert.

    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 Zusammenführung von Objektattributen

Im erweiterten Modus werden die Objekteigenschaften vom Compiler reduziert, um die Namensverkürzung vorzubereiten. Der Compiler transformiert beispielsweise Folgendes:

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

in Folgendes ändern:

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

Durch das Zusammenführen von Properties kann die spätere Umbenennung effizienter erfolgen. Der Compiler kann foo$bar beispielsweise durch ein einzelnes Zeichen ersetzen.

Durch das Reduzieren der Ebenen von Properties werden jedoch auch die folgenden Praktiken gefährlich:

  • this außerhalb von Konstruktoren und Prototypmethoden verwenden:

    Durch das Reduzieren von Properties kann sich die Bedeutung des Schlüsselworts 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 solchen Fällen gibt der Compiler diese Warnung aus:

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

    Damit durch das Reduzieren von Eigenschaften Ihre Verweise auf this nicht unterbrochen werden, verwenden Sie this nur in Konstruktoren und Prototypmethoden. Die Bedeutung von this ist eindeutig, wenn Sie einen Konstruktor mit dem Keyword new aufrufen oder innerhalb einer Funktion, die ein Attribut von prototype ist.

  • Statische Methoden verwenden, ohne zu wissen, für welche Klasse sie aufgerufen werden:

    Zum Beispiel:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    Der Compiler fasst beide create-Methoden zusammen (nach dem Transpilieren von ES6 zu ES5), sodass der cls.create()-Aufruf fehlschlägt. Mit der Annotation @nocollapse können Sie das verhindern:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
  • „super“ in einer statischen Methode verwenden, ohne die Superklasse 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();

    Durch das Zusammenführen von Properties wird der folgende Code jedoch unterbrochen, auch wenn myMixin(Parent).sayHi und Parent.sayHi im nicht kompilierten Zustand gleich sind:

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

    Mit der Anmerkung /** @nocollapse */ können Sie dieses Problem vermeiden.

  • Object.defineProperties oder ES6-Getter/Setter verwenden:

    Der Compiler versteht diese Konstrukte nicht gut. ES6-Getter und -Setter werden durch Transpilierung in Object.defineProperties(...) umgewandelt. Der Compiler kann dieses Konstrukt derzeit nicht statisch analysieren und geht davon aus, dass der Zugriff auf und das Festlegen von Eigenschaften keine Nebenwirkungen hat. Dies kann gefährliche Folgen haben. Beispiel:

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

    Wird kompiliert zu:

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

    C.someProperty hat keine Nebenwirkungen und wurde daher entfernt.