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. Diewith
-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 beispielsweiseFunction.toString()
, um die Namen der Parameter einer Funktion abzurufen. Wenn Sie jedoch versuchen, die Namen von Argumenten in Ihrem Code mitargumentNames()
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()
undinitY()
in derfor
-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
infoo.bar
auffoo
. Nach der Transformation bezieht sichthis
auf die globalethis
. 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 Siethis
nur in Konstruktoren und Prototypmethoden. Die Bedeutung vonthis
ist eindeutig, wenn Sie einen Konstruktor mit dem Schlüsselwortnew
oder innerhalb einer Funktion aufrufen, die ein Attribut einesprototype
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 beidecreate
-Methoden (nach der Umwandlung von ES6 zu ES5), sodass dercls.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()
aufParent.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
gleichParent.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.