JavaScript es un lenguaje de programación que, a pesar de los años que tiene, cada vez está más de moda y se utiliza más (y no sólo para la web). En los últimos años está viviendo una época dorada que pocos quizá le vaticinaron, gracias sin duda a la popularización de las técnicas AJAX tan necesarias para la Web 2.0.
Desde mediados de los '90 cualquier programador Web tenía que conocer como mínimo sus fundamentos (de ahí que mi libro sobre el tema vendiera en el año 2.000 un número indecente de ejemplares y fuera de los más vendidos de Amazon en su categoría en todos los idiomas). Sin embargo, lo cierto es que poca gente va más allá de su superficie, y la mayor parte de los programadores Web se quedan en esos fundamentos. Incluso muchos programadores lo rechazan y lo consideran un lenguaje de segunda categoría por ser debilmente tipado e interpretado. Esta misma semana un programador de mi empresa, recién salido de la Universidad, me dijo que en la carrera les desaconsejaban utilizarlo. ¡Lo que es el desconocimiento!
JavaScript es un lenguaje potentísimo que permite hacer verdaderas maravillas bien utilizado. Hay bibliotecas como jQuery, YUI, Dojo, Microsoft Ajax Library, etc... que son una virguería. Y el lenguaje en sí tiene algunas características únicas que ya quisieran para sí otros lenguajes que a priori son más "profesionales".
Una de estas características es de la que voy a hablar en este post. Se trata de las Closures o Clausuras, provinientes de la programación funcional y muy útiles en algunas ocasiones. Están presentes también en otros lenguajes como C#, Groovy o Lisp. Es muy probable que incluso las hayas utilizado en algún desarrollo de JavaScript sin haberte percatado. Tienen muchas aplicaciones en el actual desarrollo orientado a objetos con JavaScript, por lo que todo programador de JavaScript debería conocerlas.
El ámbito de las variables en JavaScript
Lo habitual cuando trabajamos con JavaScript es fijarnos en que hay dos tipos de ámbitos para las variables: Global y Local. Por ejemplo, en el siguiente fragmento de código JavaScript:
var glo = 1;
function PruebaLocales() {
var loc = 2:
alert(glo); //Muestra un 1
alert(loc); //Muestra un 2
}
alert(glo); //Muestra un 1
alert(loc); //Fallará
La variable global 'glo' es accesible desde cualquier parte de nuestro script, ya que se a definido fuera de cualquier otro ámbito, de manera independiente. Sin embargo la variable local 'loc' se ha definido dentro de una función, y por lo tanto sólo tiene validez dentro de ésta, y por lo tanto la última instrucción fallará, puesto que estamos intentando llamarla desde fuera de la función.
Como resumen se podría decir que nuestro código puede acceder a variables que tengan un ámbito mayor que en el que se encuentran pero no menor. Así, desde una función sólo podremos acceder a las variables propias de dicha función, que no son accesibles desde otros ámbitos.
La verdadera regla de acceso a variables
Ahora vamos a considerar el siguiente código pefectamente válido en JavaScript:
function pruebaClosure() {
var loc = "¡Hola closure!";
this.muestraMensaje = function() {
alert(loc);
};
}
var obj = new pruebaClosure();
obj.muestraMensaje(); //Muestra el mensaje de saludo
En este fragmento hemos definido una función/clase que dispone de una función interna definida como método de la clase. Es similar a tener definida una función dentro de otra función (anidada). Lo curioso en este código es que funciona perfectamente y muestra el mensaje contenido en la variable local. Es decir, que en este caso la regla de ámbito parece que se rompe ya que tenemos una función que es capaz de acceder a una variable que no pertenece a la propia función.
Sin embargo no es así y la regla sigue siendo vigente. La verdadera regla de acceso a variables es que una línea de nuestro código sólo puede acceder a variables que tengan un ámbito mayor o igual que el suyo propio. Y en este caso se está cumpliendo ya que la variable 'loc' tiene un ámbito mayor que el de la línea de código que lanza la alerta, que tiene un ámbito (el de su función) todavía más restringido.
Piénsalo como una jerarquía donde los niveles inferiores siempre tienen acceso a los superiores. Si estuviésemos hablando de un país la cosa sería que los ciudadanos de una ciudad tienen acceso a los servicios públicos de la ciudad, la región y el país, pero en niveles superiores no es así, de modo que un habitante de la región pero no de la ciudad que estamos considerando tiene acceso a los servicios de la región y del pais, pero no a los de la ciudad por no ser un ciudadano de la misma. Espero haberme explicado :-)
El verdadero aspecto de las Closures
Las clausuras se llaman así porque en realidad lo que representan es a funciones que tienen acceso a una serie de variables, libres de su propio ámbito (están en un ámbito superior), y que hacen uso de ellas mediante una sintaxis externa que es la que las establece y le da el sentido (las cierra o clausura, de ahí el nombre).
Lo explicaré mejor con un ejemplo más apropiado como el siguiente código JavaScript:
function concatenar(s1) {
return function(s2) {
return s1 + ' ' + s2;
};
}
var diHola = concatenar("Hola");
alert( diHola("visitante") );
Vaya. Sí que es enrevesado ¿no?. Lo que muestra en pantalla es un mensaje como este:
Es decir, en la práctica gracias al uso de una clausura hemos definido una función que permite asignar valores para ejecución retardada. Esto es algo realmente útil, y no es la única aplicación práctica de las clausuras, pero gracias a ello podemos habilitar todo un sistema de ejecución en diferido de funciones JavaScript. Esto nos permite crear punteros a funciones pre-parametrizadas y con ello crear un sistema de eventos, lanzar acciones periódicas complejas y particularizadas con setInterval o SetTimeout... y muchas otras cosas.
Ejemplo de ejecución diferida
Por ejemplo, consideremos un ejemplo sencillo pero revelador de cómo funcionan estas técnicas que he mencionado. Se trata de lanzar una función con parámetros cada cierto tiempo, con setInterval o con setTimeOut. Esta dos funciones toman como parámetros una referencia a la función a ejecutar y el tiempo en milisegundos al cabo del cual debe ejecutarse (periódicamente o sólo una vez, según sea uno u otro método). Pero tienen una limitación: no permiten pasar parámetros a la función a ejecutar. Así que no sirve para llamar a funciones que tomen parámetros para particularizar su funcionamiento.
La solución chapucera sería definir una función sin parámetros que a su veces llame a la que queremos llamar nosotros con los parámetros requeridos. Una solución mucho más elegante es usar una clausura, como en este ejemplo:
function moverElemento(elto, x, y)
{
return function(){
elto.style = "position:absolute; x:"+ x + ";y:" + "y;";
};
}
var mover = moverElemento(getElementById("miDiv"), 0, 0);
setTimeout(mover, 500);
En este caso he utilizado un ejemplo sencillo (la sintaxis de posicionamiento puede que ni esté bien) pero lo que quería ilustrar es cómo usando el artificio de las closures es posible definir punteros a funciones JavaScript parametrizadas en su comportamiento.
Piénsalo y verás las amplias posibilidades que abre. Una de ellas, que contaré en un próximo post es la posibilidad, a priori imposible, de definir variables privadas en objetos JavaScritp, protegiendo el acceso a las mismas de forma que los programadores irresponsables no pudan tocarlas y tengan que ir a través de los cauces que les marquemos.
Las closures abren todo un mundo de posibilidades para la programación JavaScript.
¡Espero que te resulte interesante!
Para saber más: Programación avanzada con JavaScript y ECMAScript