El compilador de Closure espera que su entrada de JavaScript cumpla con algunas restricciones. Cuanto mayor sea el nivel de optimización que le pidas al compilador, más restricciones impondrá este al código JavaScript de entrada.
En este documento, se describen las principales restricciones para cada nivel de optimización. Consulta también esta página de la wiki para conocer las suposiciones adicionales que realiza el compilador.
Restricciones para todos los niveles de optimización
El compilador impone las siguientes dos restricciones en todo el código JavaScript que procesa, para todos los niveles de optimización:
El compilador solo reconoce ECMAScript.
ECMAScript 5 es la versión de JavaScript compatible en casi todas partes. Sin embargo, el compilador también admite muchas de las funciones de ECMAScript 6. El compilador solo admite funciones oficiales del lenguaje.
Las funciones específicas del navegador que cumplen con la especificación de lenguaje ECMAScript adecuada funcionarán bien con el compilador. Por ejemplo, los objetos ActiveX se crean con sintaxis de JavaScript legal, por lo que el código que crea objetos ActiveX funciona con el compilador.
Los mantenedores del compilador trabajan activamente para admitir nuevas versiones del lenguaje y sus funciones. Los proyectos pueden especificar qué versión del lenguaje ECMAScript pretenden usar con la marca
--language_in
.El compilador no conserva los comentarios.
Todos los niveles de optimización del compilador quitan los comentarios, por lo que el código que depende de comentarios con formato especial no funciona con el compilador.
Por ejemplo, debido a que el compilador no conserva los comentarios, no puedes usar los "comentarios condicionales" de JScript directamente. Sin embargo, puedes evitar esta restricción si encapsulas los comentarios condicionales en expresiones
eval()
. El compilador puede procesar el siguiente código sin generar un error:x = eval("/*@cc_on 2+@*/ 0");
Nota: Puedes incluir licencias de código abierto y otro texto importante en la parte superior del resultado del compilador con la anotación @preserve.
Restricciones para SIMPLE_OPTIMIZATIONS
El nivel de optimización Simple cambia el nombre de los parámetros de funciones, las variables locales y las funciones definidas de forma local para reducir el tamaño del código. Sin embargo, algunas construcciones de JavaScript pueden interrumpir este proceso de cambio de nombre.
Evita las siguientes construcciones y prácticas cuando uses SIMPLE_OPTIMIZATIONS
:
with
:Cuando usas
with
, el compilador no puede distinguir entre una variable local y una propiedad de objeto con el mismo nombre, por lo que cambia el nombre de todas las instancias del nombre.Además, la instrucción
with
dificulta la lectura del código para los humanos. La instrucciónwith
cambia las reglas normales de resolución de nombres y puede dificultar incluso al programador que escribió el código identificar a qué se refiere un nombre.eval()
:El compilador no analiza el argumento de cadena de
eval()
, por lo que no cambiará el nombre de ningún símbolo dentro de este argumento.Representaciones de cadenas de los nombres de funciones o parámetros:
El compilador cambia el nombre de las funciones y los parámetros de las funciones, pero no cambia ninguna cadena de tu código que haga referencia a funciones o parámetros por su nombre. Por lo tanto, debes evitar representar nombres de funciones o parámetros como cadenas en tu código. Por ejemplo, la función de biblioteca
argumentNames()
de Prototype usaFunction.toString()
para recuperar los nombres de los parámetros de una función. Sin embargo, si bienargumentNames()
podría tentarte a usar los nombres de los argumentos en tu código, la compilación en modo Simple interrumpe este tipo de referencia.
Restricciones para ADVANCED_OPTIMIZATIONS
El nivel de compilación ADVANCED_OPTIMIZATIONS
realiza las mismas transformaciones que SIMPLE_OPTIMIZATIONS
y también agrega el cambio de nombre global de propiedades, variables y funciones, la eliminación de código no utilizado y el aplanamiento de propiedades. Estos nuevos pases imponen restricciones adicionales en el código JavaScript de entrada. En general, el uso de las funciones dinámicas de JavaScript impedirá el análisis estático correcto de tu código.
Implicaciones del cambio de nombre de variables, funciones y propiedades globales:
El cambio de nombre global de ADVANCED_OPTIMIZATIONS
hace que las siguientes prácticas sean peligrosas:
Referencias externas no declaradas:
Para cambiar el nombre de las variables, las funciones y las propiedades globales correctamente, el compilador debe conocer todas las referencias a esos elementos globales. Debes informar al compilador sobre los símbolos que se definen fuera del código que se está compilando. En Advanced Compilation and Externs, se describe cómo declarar símbolos externos.
Uso de nombres internos no exportados en código externo:
El código compilado debe exportar todos los símbolos a los que hace referencia el código sin compilar. En Compilación avanzada y Externs, se describe cómo exportar símbolos.
Usa nombres de cadenas para hacer referencia a las propiedades de los objetos:
El compilador cambia el nombre de las propiedades en el modo avanzado, pero nunca cambia el nombre de las cadenas.
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
Si necesitas hacer referencia a una propiedad con una cadena entre comillas, siempre usa una cadena entre comillas:
var x = { 'unrenamed_property': 1 }; x['unrenamed_property']; // This is OK. if ( 'unrenamed_property' in x ) {}; // This is OK
Cómo hacer referencia a las variables como propiedades del objeto global:
El compilador cambia el nombre de las propiedades y las variables de forma independiente. Por ejemplo, el compilador trata las siguientes dos referencias a
foo
de manera diferente, aunque sean equivalentes:var foo = {}; window.foo; // BAD
Este código podría compilarse de la siguiente manera:
var a = {}; window.b;
Si necesitas hacer referencia a una variable como propiedad del objeto global, siempre hazlo de esa manera:
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 }
El compilador no comprende que se llama a
initX()
yinitY()
en el buclefor
, por lo que quita ambos métodos.Ten en cuenta que, si pasas una función como parámetro, el compilador puede encontrar llamadas a ese parámetro. Por ejemplo, el compilador no quita la función
getHello()
cuando compila el siguiente código en modo avanzado.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.
Implicaciones del aplanamiento de propiedades de objetos
En el modo avanzado, el compilador contrae las propiedades del objeto para prepararse para el acortamiento del nombre. Por ejemplo, el compilador transforma lo siguiente:
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
en esto:
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
Este aplanamiento de propiedades permite que el paso de cambio de nombre posterior cambie el nombre de manera más eficiente. Por ejemplo, el compilador puede reemplazar foo$bar
por un solo carácter.
Sin embargo, el aplanamiento de propiedades también hace que las siguientes prácticas sean peligrosas:
Uso de
this
fuera de los constructores y los métodos de prototipos:El aplanamiento de propiedades puede cambiar el significado de la palabra clave
this
dentro de una función. Por ejemplo:var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD foo.bar("hello");
se convierte en:
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Antes de la transformación, el elemento
this
dentro defoo.bar
hace referencia afoo
. Después de la transformación,this
hace referencia althis
global. En casos como este, el compilador produce la siguiente advertencia:"WARNING - dangerous use of this in static method foo.bar"
Para evitar que el aplanamiento de propiedades interrumpa tus referencias a
this
, usathis
solo dentro de los constructores y los métodos de prototipos. El significado dethis
es inequívoco cuando llamas a un constructor con la palabra clavenew
o dentro de una función que es una propiedad de unprototype
.Usa métodos estáticos sin saber en qué clase se llaman:
Por ejemplo, si tienes lo siguiente:
el compilador contraerá ambos métodosclass A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
create
(después de la transpilación de ES6 a ES5), por lo que fallará la llamada acls.create()
. Puedes evitarlo con la anotación@nocollapse
:class A { /** @nocollapse */ static create() { return new A(); } } class B { /** @nocollapse */ static create() { return new A(); } }
Uso de super en un método estático sin conocer la superclase:
El siguiente código es seguro porque el compilador sabe que
super.sayHi()
hace referencia aParent.sayHi()
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends Parent { static sayHi() { super.sayHi(); } } Child.sayHi();
Sin embargo, el aplanamiento de propiedades interrumpirá el siguiente código, incluso si
myMixin(Parent).sayHi
es igual aParent.sayHi
sin compilar:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends myMixin(Parent) { static sayHi() { super.sayHi(); } } Child.sayHi();
Evita esta interrupción con la anotación
/** @nocollapse */
.Uso de Object.defineProperties o de los métodos get/set de ES6:
El compilador no comprende bien estas construcciones. Los métodos get y set de ES6 se transforman en Object.defineProperties(…) a través de la transpilación. Actualmente, el compilador no puede analizar de forma estática esta construcción y supone que el acceso y la configuración de las propiedades no tienen efectos secundarios. Esto puede tener repercusiones peligrosas. Por ejemplo:
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Se compila en lo siguiente:
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; }}});
Se determinó que C.someProperty no tenía efectos secundarios, por lo que se quitó.