Компилятор Closure ожидает, что входные данные JavaScript будут соответствовать нескольким ограничениям. Чем выше уровень оптимизации, который вы просите выполнить компилятор, тем больше ограничений компилятор накладывает на входной JavaScript.
В этом документе описаны основные ограничения для каждого уровня оптимизации. См. также эту вики-страницу для дополнительных предположений, сделанных компилятором.
Ограничения для всех уровней оптимизации
Компилятор накладывает следующие два ограничения на весь обрабатываемый им JavaScript для всех уровней оптимизации:
Компилятор распознает только ECMAScript.
ECMAScript 5 — это версия JavaScript, поддерживаемая почти повсеместно. Однако компилятор также поддерживает многие функции ECMAScript 6. Компилятор поддерживает только функции официального языка.
Специфичные для браузера функции, которые соответствуют соответствующей спецификации языка ECMAScript, будут нормально работать с компилятором. Например, объекты ActiveX создаются с использованием допустимого синтаксиса JavaScript, поэтому код, создающий объекты ActiveX, работает с компилятором.
Специалисты по сопровождению компилятора активно работают над поддержкой новых языковых версий и их функций. Проекты могут указать, какую языковую версию ECMAScript они предполагают, используя флаг
--language_in
.Компилятор не сохраняет комментарии.
Все уровни оптимизации компилятора удаляют комментарии, поэтому код, основанный на специально отформатированных комментариях, не работает с компилятором.
Например, поскольку компилятор не сохраняет комментарии, вы не можете напрямую использовать «условные комментарии» JScript. Однако вы можете обойти это ограничение, заключив условные комментарии в выражения
eval()
. Компилятор может обработать следующий код без возникновения ошибки:x = eval("/*@cc_on 2+@*/ 0");
Примечание. Вы можете включить лицензии с открытым исходным кодом и другой важный текст в верхней части выходных данных компилятора, используя аннотацию @preserve.
Ограничения для SIMPLE_OPTIMIZATIONS
Уровень оптимизации Simple переименовывает параметры функций, локальные переменные и локально определенные функции для уменьшения размера кода. Однако некоторые конструкции JavaScript могут нарушить этот процесс переименования.
Избегайте следующих конструкций и методов при использовании SIMPLE_OPTIMIZATIONS
:
with
:При
with
компилятор не может отличить локальную переменную от свойства объекта с тем же именем, и поэтому переименовывает все экземпляры этого имени.Кроме того, оператор
with
делает ваш код более трудным для чтения людьми. Операторwith
изменяет обычные правила разрешения имен и может затруднить определение того, на что ссылается имя, даже программисту, написавшему код.eval()
:Компилятор не анализирует строковый аргумент
eval()
, поэтому он не будет переименовывать какие-либо символы в этом аргументе.Строковые представления имен функций или параметров:
Компилятор переименовывает функции и параметры функций, но не изменяет никаких строк в вашем коде, которые ссылаются на функции или параметры по имени. Таким образом, вам следует избегать представления имен функций или параметров в виде строк в вашем коде. Например, функция
argumentNames()
библиотеки Prototype используетFunction.toString()
для получения имен параметров функции. Но в то время какargumentNames()
может соблазнить вас использовать имена аргументов в вашем коде, компиляция в простом режиме нарушает этот вид ссылок.
Ограничения для ADVANCED_OPTIMIZATIONS
Уровень компиляции ADVANCED_OPTIMIZATIONS
выполняет те же преобразования, что и SIMPLE_OPTIMIZATIONS
, а также добавляет глобальное переименование свойств, переменных и функций, удаление мертвого кода и выравнивание свойств. Эти новые проходы накладывают дополнительные ограничения на входной JavaScript. Как правило, использование динамических функций JavaScript препятствует корректному статическому анализу вашего кода.
Последствия переименования глобальных переменных, функций и свойств:
Глобальное переименование ADVANCED_OPTIMIZATIONS
делает следующие действия опасными:
Необъявленные внешние ссылки:
Чтобы правильно переименовать глобальные переменные, функции и свойства, компилятор должен знать обо всех ссылках на эти глобальные переменные. Вы должны сообщить компилятору о символах, которые определены вне компилируемого кода. Advanced Compilation and Externs описывает, как объявлять внешние символы.
Использование неэкспортированных внутренних имен во внешнем коде:
Скомпилированный код должен экспортировать любые символы, на которые ссылается нескомпилированный код. Advanced Compilation and Externs описывает, как экспортировать символы.
Использование строковых имен для ссылки на свойства объекта:
Компилятор переименовывает свойства в расширенном режиме, но никогда не переименовывает строки.
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
Если вам нужно сослаться на свойство с помощью строки в кавычках, всегда используйте строку в кавычках:
var x = { 'unrenamed_property': 1 }; x['unrenamed_property']; // This is OK. if ( 'unrenamed_property' in x ) {}; // This is OK
Обращение к переменным как к свойствам глобального объекта:
Компилятор переименовывает свойства и переменные независимо друг от друга. Например, компилятор обрабатывает следующие две ссылки на
foo
по-разному, даже если они эквивалентны:var foo = {}; window.foo; // BAD
Этот код может быть скомпилирован в:
var a = {}; window.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 }
Компилятор не понимает, что
initX()
иinitY()
вызываются в циклеfor
, поэтому он удаляет оба этих метода.Обратите внимание, что если вы передаете функцию в качестве параметра, компилятор может найти вызовы этого параметра. Например, компилятор не удаляет
getHello()
при компиляции следующего кода в расширенном режиме.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.
Последствия выравнивания свойств объекта
В расширенном режиме компилятор сворачивает свойства объекта, чтобы подготовиться к сокращению имени. Например, компилятор преобразует это:
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
в это:
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
Это выравнивание свойств позволяет более эффективно переименовывать более поздний проход переименования. Например, компилятор может заменить foo$bar
одним символом.
Но выравнивание свойств также делает опасными следующие действия:
Используя
this
вне конструкторов и методов прототипа:Выравнивание свойств может изменить значение ключевого слова
this
внутри функции. Например:var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD foo.bar("hello");
становится:
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Перед преобразованием
this
внутриfoo.bar
ссылается наfoo
. После преобразованияthis
ссылается на глобальныйthis
. В подобных случаях компилятор выдает следующее предупреждение:"WARNING - dangerous use of this in static method foo.bar"
Чтобы сведение свойств не нарушало ваши ссылки на
this
, используйтеthis
только в конструкторах и методах прототипа. Смыслthis
однозначен, когда вы вызываете конструктор с ключевым словомnew
или внутри функции, которая является свойствомprototype
.Использование статических методов, не зная, в каком классе они вызываются:
Например, если у вас есть:
class A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
, компилятор свернет оба методаcreate
(после переноса из ES6 в ES5), поэтомуcls.create()
завершится ошибкой. Вы можете избежать этого с помощью аннотации@nocollapse
:class A { /** @nocollapse */ static create() { return new A(); } } class B { /** @nocollapse */ static create() { return new A(); } }
Использование super в статическом методе без знания суперкласса:
Следующий код безопасен, поскольку компилятор знает, что
super.sayHi()
ссылается наParent.sayHi()
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends Parent { static sayHi() { super.sayHi(); } } Child.sayHi();
Однако выравнивание свойств нарушит следующий код, даже если
myMixin(Parent).sayHi
равенParent.sayHi
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends myMixin(Parent) { static sayHi() { super.sayHi(); } } Child.sayHi();
Избегайте этой поломки с помощью аннотации
/** @nocollapse */
.Используя Object.defineProperties или геттеры/сеттеры ES6:
Компилятор плохо понимает эти конструкции. Геттеры и сеттеры ES6 преобразуются в Object.defineProperties(...) посредством транспиляции. В настоящее время компилятор не может статически анализировать эту конструкцию и предполагает, что доступ к свойствам и установка не имеют побочных эффектов. Это может иметь опасные последствия. Например:
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Компилируется в:
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 не имеет побочных эффектов, поэтому он был удален.