domingo, 3 de junio de 2012

Espacios de nombres en JavaScript (namespaces)

El manejo de espacios de nombres en JavaScript ha sido ignorado durante muchos años porque los programas eran relativamente sencillos y, normalmente, una página web no se componía de diferentes piezas de código desarrolladas por programadores o empresas diferentes. El código se incluía como un script en la página y la mayoría de las variables y funciones eran globales.

Actualmente, en JavaScript se programan aplicaciones muy complejas ( enormes a veces ) y se han desarrollado cientos de librerías que podemos importar en nuestro código. Esto hace necesario organizar de alguna manera nuestro código para asegurarnos de que nuestras variables, objetos y funciones sean únicas y no se estén sobreescribiendo ( o siendo sobreescritas ) por una variable del mismo nombre de alguna de las librerías externas que utilicemos. Nombres como getElement, counter, checkValue, showMessage, etc son muy comunes y podrían existir en casi cualquier script que importemos.

Para poder utilizar cualquier nombre con seguridad, cada programa debe definir sus variables dentro de un contenedor único. Este contenedor es el espacio de nombres (namespace).

Cada librería externa que utilicemos tendrá su propio espacio de nombres y nosotros debemos definir también el nuestro para nuestro proyecto.

¿Cómo se crea un espacio de nombres?


En JavaScript se utiliza un objeto como espacio de nombres porque no existe una sintaxis nativa para esta funcionalidad.

Por ejemplo, podemos definir el siguiente objeto:

var myProject = {};

y luego definimos nuestras variables y funciones como propiedades de este objeto:

myProject.var1 = "una variable";
myProject.var2 = "otra variable";
myProject.var3 = { uno: 1,
                   dos: 2 };
myProject.doSomething = function () { ... };

De esta forma la única variable global que definimos es myProject y todos los nombres que definimos en el proyecto estarán dentro de este contexto definido por nuestro espacio de nombres. Siempre accederemos a cualquier elemento con el prefijo myProject delante.

La forma de definir las variables dentro del objeto no importa, podemos hacerlo directamente al crearlo:

var myProject = {
    var1:  "una variable",
    var2 = "otra variable",
    var3 = { uno: 1,
             dos: 2 };
    doSomething = function () { ... }

}

o utilizar el patrón módulo para tener también propiedades privadas.

Comprobar si el espacio de nombres está creado


Antes de crear el espacio de nombres es necesario comprobar si ya está creado. Si el namespace ya está definido utilizaremos el objeto ya creado. Esto es especialmente importante cuando nuestra aplicación tiene varios módulos y utilizamos espacios de nombres anidados:

myProject.userInterface
myProject.ajaxHandler
myProject.eventManager

Normalmente cada módulo está en un fichero separado y no podemos hacer:

var myProject = {};
myProject.usrInterface = { 
    //module logic here
}

Porque si myProject ya existe estaríamos borrando todo lo que contenga (todos los otros modulos).

Una forma común de hacer la comprobación es:

var myProject = myProject || {};

o también:

myProject || ( myProject = {} )

En esta última expresión, la segunda parte sólo se ejecuta si myProject es false.

Una función para crear el espacio de nombres


Cuando el proyecto es muy grande puede haber varios niveles de anidamiento en el namespace:

myProject.ui.list
myProject.ui.userForm
myProject.utils.validateInput
myProject.core.ajaxHandler
myProject.core.eventManager
...

Cada módulo tendrá que comprobar, uno a uno, si existen los namespaces de nivel superior antes de crear el suyo. Es mucho más cómodo utilizar una función para crear un espacio de nombres concreto, que se le pasa como parámetro. Todas las comprobaciones quedan encapsuladas en la función.

function createNameSpace( nameSpaceString ) {
    var names = nameSpaceString.split(".");

    //global object is the parent for first level nameSpace
    var parent = window;  
    
    //if any nameSpace level doesn't exist, create it
    for ( var i=0, imax=names.length ; i < imax; i++ ) {
        if ( !parent[names[i]] ) parent[names[i]]={};
        parent = parent[names[i]];
    }      
}

Al principio de cada módulo crearíamos el espacio de nombres de esta forma:
createNameSpace( "myProject.core.ajaxHandler" );

2 comentarios: