Kompilator Closure oczekuje, że dane wejściowe JavaScript będą zgodne z kilkoma ograniczeniami. Im wyższy poziom optymalizacji, o który prosisz kompilator, tym więcej ograniczeń nakłada on na wejściowy kod JavaScript.
W tym dokumencie opisujemy główne ograniczenia dla każdego poziomu optymalizacji. Dodatkowe założenia kompilatora znajdziesz też na tej stronie wiki.
Ograniczenia na wszystkich poziomach optymalizacji
Kompilator nakłada na cały przetwarzany kod JavaScript te 2 ograniczenia na wszystkich poziomach optymalizacji:
Kompilator rozpoznaje tylko ECMAScript.
ECMAScript 5 to wersja JavaScriptu obsługiwana niemal wszędzie. Komputer obsługuje jednak wiele funkcji ECMAScript 6. Kompilator obsługuje tylko funkcje języka urzędowego.
Funkcje specyficzne dla przeglądarki, które są zgodne z odpowiednią specyfikacją języka ECMAScript, będą działać prawidłowo z kompilatorem. Na przykład obiekty ActiveX są tworzone za pomocą prawidłowej składni JavaScript, więc kod, który tworzy obiekty ActiveX, działa z kompilatorem.
Osoby odpowiedzialne za kompilator aktywnie pracują nad obsługą nowych wersji języka i ich funkcji. Projekty mogą określać, której wersji języka ECMAScript chcą używać, za pomocą flagi
--language_in
.Kompilator nie zachowuje komentarzy.
Wszystkie poziomy optymalizacji kompilatora usuwają komentarze, więc kod, który opiera się na specjalnie sformatowanych komentarzach, nie działa z kompilatorem.
Na przykład kompilator nie zachowuje komentarzy, więc nie możesz bezpośrednio używać „komentarzy warunkowych” JScript. Możesz jednak obejść to ograniczenie, umieszczając komentarze warunkowe w wyrażeniach
eval()
. Kompilator może przetworzyć ten kod bez generowania błędu:x = eval("/*@cc_on 2+@*/ 0");
Uwaga: możesz umieścić licencje open source i inne ważne informacje u góry danych wyjściowych kompilatora, używając adnotacji @preserve.
Ograniczenia dotyczące SIMPLE_OPTIMIZATIONS
Poziom optymalizacji Simple zmienia nazwy parametrów funkcji, zmiennych lokalnych i funkcji zdefiniowanych lokalnie, aby zmniejszyć rozmiar kodu. Jednak niektóre konstrukcje JavaScriptu mogą przerwać ten proces zmiany nazwy.
Podczas korzystania z SIMPLE_OPTIMIZATIONS
unikaj tych konstrukcji i praktyk:
with
:Gdy używasz
with
, kompilator nie może odróżnić zmiennej lokalnej od właściwości obiektu o tej samej nazwie, więc zmienia nazwy wszystkich wystąpień.Ponadto instrukcja
with
utrudnia odczytanie kodu przez człowieka. Instrukcjawith
zmienia normalne reguły rozpoznawania nazw i może utrudnić nawet programiście, który napisał kod, określenie, do czego odnosi się dana nazwa.eval()
:Kompilator nie analizuje argumentu ciągu znaków funkcji
eval()
, więc nie zmieni nazw żadnych symboli w tym argumencie.Ciągi znaków reprezentujące nazwy funkcji lub parametrów:
Kompilator zmienia 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 przedstawiać nazw funkcji ani parametrów jako ciągów znaków. Na przykład funkcja biblioteki Prototype
argumentNames()
używaFunction.toString()
do pobierania nazw parametrów funkcji. ChociażargumentNames()
może zachęcać do używania w kodzie nazw argumentów, kompilacja w trybie prostym przerywa tego rodzaju odwołania.
Ograniczenia dotyczące ADVANCED_OPTIMIZATIONS
Poziom kompilacji ADVANCED_OPTIMIZATIONS
wykonuje te same przekształcenia co poziom SIMPLE_OPTIMIZATIONS
, a dodatkowo przeprowadza globalne zmienianie nazw właściwości, zmiennych i funkcji, usuwanie martwego kodu i spłaszczanie właściwości. Te nowe etapy nakładają dodatkowe ograniczenia na wejściowy kod JavaScript. Ogólnie rzecz biorąc, korzystanie z dynamicznych funkcji JavaScriptu uniemożliwia prawidłową analizę statyczną kodu.
Konsekwencje zmiany nazw zmiennych globalnych, funkcji i właściwości:
Globalna zmiana nazwy ADVANCED_OPTIMIZATIONS
sprawia, że te praktyki są niebezpieczne:
Niezadeklarowane odwołania do elementów zewnętrznych:
Aby poprawnie zmienić nazwy zmiennych globalnych, funkcji i właściwości, kompilator musi znać wszystkie odwołania do tych zmiennych globalnych. Musisz poinformować kompilator o symbolach zdefiniowanych poza kompilowanym kodem. W artykule Zaawansowana kompilacja i pliki zewnętrzne opisujemy, jak deklarować symbole zewnętrzne.
Używanie niewyeksportowanych nazw wewnętrznych w kodzie zewnętrznym:
Skompilowany kod musi eksportować wszystkie symbole, do których odwołuje się nieskompilowany kod. W artykule Zaawansowana kompilacja i pliki zewnętrzne znajdziesz informacje o eksportowaniu symboli.
Używanie nazw ciągów znaków do odwoływania się do właściwości obiektów:
Kompilator zmienia nazwy właściwości w trybie zaawansowanym, ale nigdy nie zmienia nazw ciągów znakó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łać się do właściwości z ciągiem znaków w cudzysłowie, zawsze używaj ciągu znaków w cudzysłowie:
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 zmienia nazwy właściwości i zmiennych niezależnie od siebie. Na przykład kompilator traktuje te 2 odwołania do
foo
inaczej, mimo że są one równoważne:var foo = {}; window.foo; // BAD
Ten kod może zostać skompilowany do:
var a = {}; window.b;
Jeśli chcesz odwołać się do zmiennej jako właściwości obiektu globalnego, zawsze rób to 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 wie, że
initX()
iinitY()
są wywoływane w pętlifor
, więc usuwa obie te metody.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łaszczania właściwości obiektu
W trybie zaawansowanym kompilator zwija właściwości obiektu, aby przygotować się do skrócenia nazw. Na przykład kompilator przekształca ten kod:
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
w ten sposób:
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
Spłaszczenie tej właściwości umożliwia późniejsze wydajniejsze zmienianie nazw. Kompilator może na przykład zastąpić ciąg foo$bar
pojedynczym znakiem.
Spłaszczanie właściwości sprawia też, że niebezpieczne stają się te praktyki:
Używanie
this
poza konstruktorami i metodami prototypu:Spłaszczanie właściwości może zmienić znaczenie słowa kluczowego
this
w funkcji. Na 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
this
wfoo.bar
odnosi się dofoo
. Po przekształceniuthis
odnosi się do globalnegothis
. W takich przypadkach kompilator wyświetla to ostrzeżenie:"WARNING - dangerous use of this in static method foo.bar"
Aby zapobiec uszkodzeniu odwołań do
this
w wyniku spłaszczenia właściwości, używajthis
tylko w konstruktorach i metodach prototypu. Znaczenie słowathis
jest jednoznaczne, gdy wywołujesz konstruktor za pomocą słowa kluczowegonew
lub w funkcji, która jest właściwością obiektuprototype
.Używanie metod statycznych bez wiedzy o tym, w której klasie są wywoływane:
Na przykład, jeśli masz:
kompilator zwinie obie metodyclass A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
create
(po transpilacji z ES6 do ES5), więc wywołaniecls.create()
się nie powiedzie. Możesz tego uniknąć, używając adnotacji@nocollapse
:class A { /** @nocollapse */ static create() { return new A(); } } class B { /** @nocollapse */ static create() { return new A(); } }
Używanie słowa kluczowego super w metodzie statycznej bez znajomości klasy nadrzędnej:
Poniższy 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();
Spłaszczanie właściwości spowoduje jednak, że ten kod przestanie działać, nawet jeśli
myMixin(Parent).sayHi
jest równeParent.sayHi
nieskompilowanemu:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends myMixin(Parent) { static sayHi() { super.sayHi(); } } Child.sayHi();
Unikaj tego problemu dzięki adnotacji
/** @nocollapse */
.Używanie Object.defineProperties lub getterów/setterów ES6:
Kompilator nie rozumie dobrze tych konstrukcji. Gettery i settery ES6 są przekształcane w Object.defineProperties(...) w procesie transpilacji. Obecnie kompilator nie może statycznie analizować tej konstrukcji i zakłada, że dostęp do właściwości i ich ustawianie nie powoduje efektów ubocznych. Może to mieć niebezpieczne konsekwencje. Na przykład:
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Jest kompilowany do:
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; }}});
Właściwość C.someProperty nie ma efektów ubocznych, więc została usunięta.