El problema del espacio de nombres global

Espacio de nombres
Encapsulamiento y variables globalesImagen no disponible
En la página de inicio de Wextensible.com tenía en el espacio global unas 50 variables y alrededor de 70 funciones.

Uno de los motivos por el que me he puesto a ver estos temas acerca de los closures es por el problema que se plantea cuando tenemos diversos módulos de JavaScript funcionando juntos. Pueden ser módulos nuestros o de terceros, como los de las redes sociales o los que hacen el seguimiento del tráfico del sitio (Google Analytics). O incluso los de publicidad. El caso es que una página sólo tiene un alcance global, el objeto Window. Cualquier declaración de variable global va a parar ahí. ¿Qué sucede si hay dos variables de dos módulos diferentes que colisionan en el nombre?. Y aunque esto no suceda, llenar el espacio global con un montón de variables no tiene "pinta" de ser buena cosa.

Por ejemplo, la página de inicio de este sitio Wextensible.com descarga seis módulos JavaScript. El módulo general.js contiene variables y funciones de uso general para todo el sitio; canvas-clock.js para insertar un reloj con canvas; form-emerge.js para manejar ventanas emergentes; calendar.js para insertar un calendario; slider-imagen.js para insertar slider o carrusel de imágenes; finalmente index-script.js es el módulo propio de la página que contiene el window.onload con las varíables y funciones específicas para esta página. En la página de inicio no hay módulos de terceros, como los de los botones "me gusta" de Facebook, Google+ y Twitter que si incorporo en las páginas interiores. No tengo Google Analytics ni los que se descargan con publicidad. Por lo tanto el tema podría ser incluso peor. Tantos elementos en el espacio global de nombres no puede ser bueno.

No sé si son muchos o pocos módulos JavaScript en la página principal. Es verdad que algunos de ellos aportan poco y ya debería haberlos quitado. El caso es que he querido seguirlos manteniendo a costa de la velocidad de carga para, precisamente, mejorar esa velocidad de carga con todo ese peso. Algunas acciones como minimizar documentos o disminuir el número de peticiones con Sprites CSS ya las he llevado a cabo. Otras como descargar asíncronamente todos los módulos JavaScript está pendiente y es lo que precisamente estoy haciendo ahora. Para lograr eso es necesario previamente aclarar el espacio de nombres global.

Vea la primera imagen de la serie, una captura de pantalla realizada con la herramienta Firebug en Firefox de la página de inicio de este sitio Wextensible.com. He recortado la imagen para ahorrar peso, mostrando solo el inicio de la captura de pantalla. Se ordenan primero por variables y luego las funciones. En primer lugar y debido al orden alfábetico aparecen las variables que considero a modo de constantes. En un recuento rápido hay unas 50 variables y alrededor de 70 funciones en el espacio global. Estos elementos son puestos por los diversos módulos en el espacio global. El primer problema que se plantea es el de la colisión de nombres. En mi caso sólo yo trabajo en el desarrollo de este sitio. Pero imaginemos que soy parte de un equipo y que cada uno pone lo que quiera en ese espacio. La colisión tendrá lugar más temprano que tarde. Pero también en mi caso, siempre que agregaba un módulo tenía que estar analizando si pudiera sobreescribir las variables de otros módulos.

En la segunda imagen de la serie verá como quedó el espacio de nombres de la página de inicio de este sitio tras hacer los cambios que explicaré en este tema. Sólo hay un único espacio que he denominado igual que el sitio Wextensible. Ahí están todas las variables y funciones. Hay dos variables más que llamo acortadores. Son wxG y wxL. Son referencias a elementos del espacio, por un lado declaro var wxG = Wextensible.general, haciendo referencia a las elementos incluidos dentro de Wextensible.general, todo lo que proviene del módulo general.js. Con este acortador evitamos tener que escribir la referencia más larga y, lo que es más importante, JavaScript no tiene que estar resolviéndola en cada llamada. La segunda se declara con var wxL = Wextensible.local, donde Wextensible.local contiene todos los elementos propios de la página actual, es decir, lo que proviene del módulo index-script.js. Digamos que es lo local a la página en oposición a lo general a todas las páginas.

Otro problema que se resuelve con esto es la tarea de depuración consultando los elementos con herramientas como Firebug. En la tercera imagen de la serie puede ver la captura de pantalla de una página interior de este sito, donde si que hay botones "me gusta" de las redes sociales. Se observa el espacio Wextensible junto a otras variables de terceros. Por ejemplo, Facebook agrupa todo lo suyo en el espacio FB. Pero Google+ y Twitter tienen sus elementos digamos "más dispersos". En esta situación cuando tenga que analizar el valor de alguna variable de mi sitio sólo tengo que buscar el elemento Wextensible, pues todas las variables y funciones estarán ahí.

En este tema y el siguiente intentaré explicar como podemos usar los closures para crear un espacio de nombres como el explicado antes. Y finalmente implementar un cargador de módulos que me permita descargar todos los archivos JavaScript asíncronamente. Sobre los espacios de nombres hay un par de referencias donde buscar más información:

Un ejemplo con variables globales

Veámos un primer ejemplo muy simple que declara variables y funciones globales. Al cargarse este script simplemente ejecuta una funcionGlobal() que inserta la cadena "ABC" en el elemento siguiente:

Ejemplo:

 
<div class="ejemplo-linea">
    <div id="mensaje1" style="border: gray solid 1px">&nbsp;</div>
</div>
<script>
    var variableGlobal = "ABC";
    function funcionGlobal(){
        return variableGlobal;
    }
    document.getElementById("mensaje1").innerHTML = funcionGlobal();
</script>

Al cargarse el script la variableGlobal va a parar al espacio Global, como se observa en la herramienta de desarrollo del navegador Chrome al poner un punto de interrupción antes de ejecutar el return.

Variable global

Si esa variableGlobal sólo se va a utilizar dentro de la funcionGlobal() no tiene sentido ubicarla en el espacio Global. Podemos ocultarla dentro la funcionGlobal() y utilizar el efecto closure para que siga existiendo tras la construcción del closure. Aplicando lo visto en los temas anteriores podemos presentar el ejemplo modificado:

Ejemplo:

 
<div class="ejemplo-linea">
    <div id="mensaje2" style="border: gray solid 1px">&nbsp;</div>
</div>
<script>
    var varGlobal = "UVW";
    var funGlobal = function(){
        var varGlobal = "XYZ";
        var varLocal = "123";
        return function(){
            return varGlobal + varLocal;
        };
    }();
    var valor = funGlobal();
    document.getElementById("mensaje2").innerHTML = valor;
</script>

Hemos dejado intencionadamente una variable global llamada varGlobal para corroborar que no interacciona con la del mismo nombre que está dentro de la función funGlobal. Esta función es ejecutada con var funGlobal() = function(){...}() al cargarse la página construyendo el closure que devuelve la función interior. Con var valor = funGlobal() realmente estamos llamando a la función interior. Observe como la herramienta de desarrollo de Chrome nos muestra el estado antes de finalizar la ejecución de esta llamada a funGlobal():

Variables globales en closure

El resultado es que hemos ocultado en el closure las variables varLocal = "123" y varGlobal = "XYZ", de tal forma que ésta última no colisiona con la del mismo nombre que está en el espacio Global.

Diseñando un espacio de nombres (namespace)

Lo visto en el apartado anterior es bueno para desalojar el espacio Global de un exceso de variables y evitar posibles colisiones de nombres. En el último ejemplo ocultamos todas las variables locales en la función funGlobal. Pero aún así la función funGlobal sigue estando en Global. Una forma de evitar este problema es poner todo lejos del espacio Global. Veámos este ejemplo:

Ejemplo:

 
<div class="ejemplo-linea">
    <div id="mensaje3" style="border: gray solid 1px">&nbsp;</div>
</div>
<script>
    //Asignación inicial a un objeto literal vacío o a si mismo
    //si ya fue creado previamente.
    var miEspacio = miEspacio || {};
    //Envolvemos el módulo dentro de una función auto-ejecutable
    (function(namespace){
        namespace.funGlobal = function(){
            var varLocal = "Estoy en el closure";
            return function(){
                return varLocal;
            };
        }();
    })(miEspacio);
    var valor = miEspacio.funGlobal();
    document.getElementById("mensaje3").innerHTML = valor;
</script>

Patrón Namespace JavaScript Este patrón de diseño JavaScript, que se basa en el expuesto en la publicación de Addy Osmani Namespacing Patterns, declara una variable miEspacio que será la que contendrá todo el espacio de variables de la página. Si esa variable ya fue declarada anteriormente ahora la vuelve a referenciar. En otro caso le asigna un objeto vacío. Supongamos que funGlobal es un módulo y queremos introducirla en nuestro espacio global miEspacio. Usamos una función anónima auto-ejecutable que asignará esa función a miEspacio en el momento de cargar el script. Luego invocamos la función con miEspacio.funGlobal() que nos devuelve la variable varLocal.

En la imagen puede ver como funGlobal creó un closure almacenando esa variable varLocal.

El espacio único de nombres

El ejemplo anterior es un patrón para usar cuando tenemos que crear múltiples espacios en la página. Si sólo vamos a usar un único espacio podríamos simplificarlo así:

Ejemplo:

 
<div class="ejemplo-linea">
    <div id="mensaje4" style="border: gray solid 1px">&nbsp;</div>
</div>
<script>
    //Asignación inicial a un objeto literal vacío o a si mismo
    //si ya fue creado previamente.
    var miEspacio2 = miEspacio2 || {};
    //Envolvemos el módulo dentro de una función auto-ejecutable
    //y la asignamos al espacio de nombres
    miEspacio2.funGlobal = (function(){
        var varLocal = "Y yo también estoy en el closure";
        return function(){
            return varLocal;
        };
    })();
    var valor2 = miEspacio2.funGlobal();
    document.getElementById("mensaje4").innerHTML = valor2;
</script>

Patrón Namespace Único JavaScript Ahora sólo tenemos dos elementos function en el código cuando antes teníamos tres, por lo que nos ahorramos la primera construción de (function(namespace){...})(miEspacio). En este caso suponemos que tenemos un único espacio para toda la página llamado miEspacio2. Por tanto todos los módulos se asignan a ese espacio. La captura de pantalla muestra que el closure se comporta igual que en el ejemplo anterior.