Kompilacja Closure Compiler oczekuje, że dane wejściowe JavaScriptu będą zgodne z kilkoma ograniczeniami. Im wyższy poziom optymalizacji, w jakim oczekujesz kompilatora, tym więcej ograniczeń wprowadza on w danych wejściowych JavaScript.
W tym dokumencie opisano główne ograniczenia obowiązujące na każdym poziomie optymalizacji. Dodatkowe założenia przyjęte przez kompilatora znajdziesz też na tej stronie wiki.
Ograniczenia na wszystkich poziomach optymalizacji
Kompilator nakłada następujące 2 ograniczenia dotyczące całego kodu JavaScriptu, który jest przetwarzany, na wszystkich poziomach optymalizacji:
Kompilator rozpoznaje tylko ECMAScript.
ECMAScript 5 to wersja JavaScript obsługiwana prawie wszędzie. Kompilator obsługuje jednak wiele funkcji dostępnych w ECMAScript 6. Kompilator obsługuje tylko oficjalne funkcje językowe.
Funkcje właściwe dla przeglądarki, które są zgodne z odpowiednią specyfikacją języka ECMAScript, będą działać z kompilatorem. Na przykład obiekty ActiveX są tworzone z użyciem legalnej składni JavaScript, więc kod tworzący obiekty ActiveX działa z kompilatorem.
Konstruktorzy aktywnie pomagają w obsłudze nowych wersji językowych i funkcji. Projekty mogą określać wersję językową ECMAScript, używając flagi
--language_in
.Compiler nie zachowuje komentarzy.
Wszystkie poziomy optymalizacji kompilatora usuwają komentarze, więc kod oparty na specjalnie sformatowanych komentarzach nie działa w kompilatorze.
Ponieważ kompilator nie zachowuje komentarzy, nie można bezpośrednio korzystać z „komentarzy warunkowych” JScript. Możesz jednak obejść to ograniczenie, dodając komentarze warunkowe do wyrażeń
eval()
. Kompilator może przetworzyć ten kod bez generowania błędu:x = eval("/*@cc_on 2+@*/ 0");
Uwaga: u góry wyników kompilatora możesz uwzględnić licencje open source i inny ważny tekst za pomocą adnotacji @preserve.
Ograniczenia dotyczące SIMPLE_OPTIMIZATIONS
Prosty poziom optymalizacji zmienia parametry funkcji, zmienne lokalne i funkcje zdefiniowane lokalnie, aby zmniejszyć rozmiar kodu. Jednak niektóre konstrukcje JavaScriptu mogą ten proces zmienić.
Jeśli używasz SIMPLE_OPTIMIZATIONS
, unikaj tych konstrukcji i praktyk:
with
:Gdy używasz metody
with
, kompilator nie rozróżnia zmiennej lokalnej i właściwości obiektu o tej samej nazwie, więc zmienia nazwę wszystkich wystąpień tej nazwy.Ponadto instrukcja
with
utrudnia użytkownikom czytanie kodu. Instrukcjawith
zmienia normalne reguły rozpoznawania nazw i może utrudniać programistę, która napisała kod, aby zidentyfikować nazwę.eval()
:Kompilator nie przeanalizuje argumentu ciągu znaków
eval()
, więc nie zmieni nazw żadnych symboli w tym argumencie.Ciągi reprezentujące nazwy funkcji lub parametrów:
Kompilator zmienił nazwy funkcji i parametrów funkcji, ale nie zmienia żadnych ciągów znaków w kodzie, które odwołują się do funkcji lub parametrów według nazwy. Dlatego w kodzie nie należy reprezentować nazw funkcji ani parametrów jako ciągów znaków. Na przykład funkcja biblioteki Prototype
argumentNames()
używaFunction.toString()
do pobrania nazw parametrów funkcji. ChoćargumentNames()
może wydawać się kuszący, aby użyć w kodzie nazw argumentów, to w kompilacji w trybie prostym takie pliki są tego rodzaju.
Ograniczenia dla ADVANCED_OPTIMIZATIONS
Poziom kompilacji ADVANCED_OPTIMIZATIONS
jest taki sam jak przekształcenie SIMPLE_OPTIMIZATIONS
, a także powoduje globalne zmiany nazw usług, zmiennych i funkcji, eliminację martwego kodu i spłaszczenie usługi. Te nowe karty nakładają dodatkowe ograniczenia na wejściowy kod JavaScript. Ogólnie rzecz biorąc, używanie dynamicznych funkcji JavaScriptu uniemożliwia prawidłowe analizowanie statycznego kodu.
Konsekwencje zmiany zmiennej globalnej, funkcji i właściwości:
Globalna zmiana nazwy usługi ADVANCED_OPTIMIZATIONS
sprawia, że następujące praktyki są niebezpieczne:
Niezadeklarowane pliki referencyjne:
Aby nazwy i funkcje globalne oraz ich funkcje były prawidłowe, kompilator musi znać wszystkie odwołania do tych zmiennych globalnych. Musisz poinformować kompilator o symbolach zdefiniowanych poza skompilowanym kodem. Zbiór zaawansowanych i zaawansowanych opisuje, jak deklarować symbole zewnętrzne.
Używanie niewyeksportowanych nazw wewnętrznych w kodzie zewnętrznym:
Skompilowany kod musi wyeksportować wszystkie symbole, których dotyczy nieskompilowany kod. Zaawansowane kompilacje i testy opisuje sposób eksportowania symboli.
Odwoływanie się do właściwości obiektu przy użyciu nazw ciągów:
Kompilator zmienia nazwy właściwości w trybie zaawansowanym, ale nigdy nie zmienia nazw ciągów.
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
Jeśli musisz odwoływać się do właściwości z ciągiem cytowanym, zawsze używaj tego ciągu:
var x = { 'unrenamed_property': 1 }; x['unrenamed_property']; // This is OK. if ( 'unrenamed_property' in x ) {}; // This is OK
Odwoływanie się do zmiennych jako właściwości obiektu globalnego:
Kompilator niezależnie zmienia nazwy właściwości i zmiennych. Na przykład kompilator traktuje poniższe 2 odwołania do zmiennej
foo
, choć są one równoważne:var foo = {}; window.foo; // BAD
Ten kod może się skompilować, aby:
var a = {}; window.b;
Jeśli jako wartość obiektu globalnego musisz odwoływać się do zmiennej, zawsze używaj jej w ten sposób:
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 }
Kompilator nie rozumie, że wywołania
initX()
iinitY()
są wywoływane w pętlifor
, dlatego obie te metody zostaną usunięte.Pamiętaj, że jeśli przekażesz funkcję jako parametr, kompilator może znaleźć wywołania tego parametru. Na przykład kompilator nie usuwa funkcji
getHello()
podczas kompilowania poniższego kodu w trybie zaawansowanym.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.
Konsekwencje spłaszczenia obiektu
W trybie zaawansowanym kompilator zwija właściwości obiektu, aby przygotować się do skrócenia nazwy. Na przykład kompilator przekształca to:
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
w:
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
Ta zmiana właściwości umożliwia późniejszą zmianę nazwy karty. Kompilator może zastąpić foo$bar
jednym znakiem.
Jednak takie rozwiązanie zmniejsza też ryzyko:
Używanie metody
this
poza konstruktorami i prototypami:Scalanie właściwości może zmieniać znaczenie słowa kluczowego
this
w funkcji. Przykład:var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD foo.bar("hello");
zmienia się w:
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Przed przekształceniem element
this
w obrębiefoo.bar
odnosi się dofoo
. Po przekształceniuthis
odnosi się do globalnego elementuthis
. W takich przypadkach kompilator generuje takie ostrzeżenie:"WARNING - dangerous use of this in static method foo.bar"
Aby zapobiec ograniczeniu rozprzestrzeniania się właściwości w odniesieniu do obiektu
this
, używaj właściwościthis
tylko w konstruktorze i prototypach. Znaczenie wartościthis
jest jednoznaczne z wywołaniem konstruktora ze słowem kluczowymnew
lub w ramach funkcji, która jest właściwościąprototype
.Za pomocą metod statycznych bez wiedzy klasy, do której zostały wywołane:
Jeśli masz na przykład:
class A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
Kompilator zwinie obie metodycreate
(po wykonaniu transpilacji z ES6 do ES5), a wywołaniecls.create()
się nie powiedzie. Możesz tego uniknąć, korzystając z adnotacji@nocollapse
:class A { /** @nocollapse */ static create() { return new A(); } } class B { /** @nocollapse */ static create() { return new A(); } }
Używanie superwersji w metodzie statycznej bez znajomości klasy:
Ten kod jest bezpieczny, ponieważ kompilator wie, że
super.sayHi()
odnosi się doParent.sayHi()
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends Parent { static sayHi() { super.sayHi(); } } Child.sayHi();
Rozbudowywanie właściwości spowoduje jednak uszkodzenie poniższego kodu, nawet jeśli
myMixin(Parent).sayHi
ma wartośćParent.sayHi
nieskompilowaną:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends myMixin(Parent) { static sayHi() { super.sayHi(); } } Child.sayHi();
Aby uniknąć tych przerw, dodaj adnotację
/** @nocollapse */
.Przy użyciu obiektu Object.defineProperties lub get ES6:
Kompilator nie do końca rozumie te konstrukcje. Metoda pobierania i ustawiania ES6 jest przekształcana w obiekt Object.defineProperties(...) przez transpilację. Obecnie kompilator nie może statycznie analizować tego konstrukcji i przyjmuje, że właściwości i ustawione właściwości są efekty uboczne. Może to wiązać się z niebezpiecznymi konsekwencjami. Przykład:
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Kompilacja:
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; }}});
Uznano, że usługa C.someProperty nie ma żadnych efektów ubocznych, dlatego została usunięta.