Clousures en Javascript ¡extendiendo los límites! - El Mundo de Angular

Clousures en Javascript ¡extendiendo los límites!

Los Clousures es una característica que trae el lenguaje Javascript, muy útil, pero si no tenemos cuidado nuestro código se puede comportar de forma extraña. Básicamente, Clousure es un mecanismo que tiene el lenguaje para «extender» el scope de ciertas variables dentro de un Execution Context.

Pero es más fácil verlo con un ejemplo, suponéte que tenés el siguiente código:


function saludo(elsaludo) {
  return function (nombre) {
    console.log(elsaludo+' '+nombre);
  }
}



var decirHola = saludo('Hola');
decirHola('Gustavo');

Una función que recibe un argumento»elsaludo» y que devuelve una función (que también recibe una argumento «nombre») y ésta imprime ambos argumentos. Esto imprime en consola al ejecutarla:
 
‘Hola Gustavo’
¿Ves algo raro? Sí, exacto, ¿cómo la función «interna» puede «conocer» la variable «elsaludo» sin estar en el contexto de la función (o en el contexto global)?. Bueno, eso es gracias a los Clousures, nuestro nuevo mecanismo para extender el scope de variables. Analicemos en «tiempo de ejecución» que pasa. Suponéte que ejecutamos la función de la siguiente forma:

var decirHola = saludo('Hola');
decirHola('Gustavo');

Como podemos ver, Clousure es extender el scope de una variable a otro Execution Context. Y como vemos en el ejemplo, no es necesario que el Execution Context donde se encontraba nuestra variable exista. En nuestro ejemplo, el Execution Context de la función «saludo» se destruye, pero la variable elsaludo sigue existiendo en el scope de la función anónima, gracias al mecanismo de Clousure.
Interesante, ¿no?.

Y ahora, las partes «raras»

Bien, ahora una consulta, para vos ¿qué imprime esto en pantalla?

 



const arr = [1, 2, 3, 4];
var functions = [];
for (var i in arr) {
functions.push(function() {
        console.log(arr[i]);
    });
}
functions[0]();
functions[1]();
functions[2]();
functions[3]();


Si tu respuesta es 4 veces 4, estás en lo correcto! si tu respuesta fue WAAAAAAAAA!!! ahora te explico que pasó ;)
Acá sólo creamos variables que están visibles en el Execution Context Global. A continuación, vamos a recorrer cuatro veces el array «arr» creando una función para cada ítem de «arr«, y pasándole como parámetro el valor «arr[i]» (para luego imprimir el valor de arr[i]).
Por si no lo sabías, el método push es un método de los arrays de Javascript para agregar un elemento a un array, ademas podemos agregar una función a un array porque las funciones son first class. En nuestro caso, estamos agregando una función al array «functions«. Cabe aclarar también que sólo creamos las funciones pero no las ejecutamos (eso lo hacemos al salir del for)
 
 Una vez creado el array de 4 funciones, podemos ejecutarlas. Recordemos que un array inicia en el índice 0, así que nuestras 4 funciones van del índice 0 al 3. Como cada elemento del array contiene una función, podemos ejecutarla de la siguiente manera:

functions[0]();

Como vemos, los Clousures no crean variables sino que, le pasan una referencia de una variable de un Execution Context a otro Execution Context. Es por eso que la variable «i«, que es la misma referencia para las 4 funciones guardadas en el array «functions«, vale lo mismo para las 4 funciones al imprimir el valor de arr[i].
Bien, ¿y cómo resolvemos ésto?, ¿cómo hacemos que cada función tenga su propia variable sin interferir en las otras llamadas?.
Hay varias formas:

Forma 1: usando let

En ECSMAC2015 se incorporó una palabra reservada «let«, que se usa para declarar variables y que su scope esté a nivel de bloque, en este caso nuestro código lo cambiamos por éste otro:


const arr = [1, 2, 3, 4];
for (var i in arr) {
    let j = i;
    setTimeout(function() {
        console.log(arr[j]);
    }, 1000);
}

Forma 2: usando IIFE

Si no sabés lo que es IIFE fijáte en este post. ¡Ahora sí, vamos a sacarle provecho a las IIFE! Por si no lo recordás, una de las características de las IIFE es que se pueden crear scopes dentro de las IIFE, de esta forma se pueden «encerrar» variables en un scope determinado sin que otra parte del código (incluido Clousures) interfiera en nuestro scope.


const arr = [1, 2, 3, 4];
var functions = [];

for (var i in arr) {
functions.push(
(function(j){
return function() {
console.log(arr[j]);
}
})(i)
);
}
console.log(functions);
functions[0]();
functions[1]();
functions[2]();
functions[3]();

Este código que parece complicado, consiste sólo en aplicar lo que sabemos de IIFE a nuestro ejemplo:

Extendiendo el scope de variables, no creando variables nuevas

Como vemos en el caso de estudio anterior, lo que hacen los Clousures no es crear variables nuevas  sino usar las variables que ya existen en otros Execution Context. Es por eso que, si alguno de los Execution Context que tenga acceso a una variable «compartida» por otro Execution Context, modifica esa variable, todos los Execution Context verán el cambio.

 

Conclusiones

Como vimos, los Clousures son un mecanismo para extender el scope de una variable de un Execution Context a otro Execution Context. Como verás, también tiene sus reglas (y muy importantes) que hay que conocer para no encontrarnos con nada raro. Mas allá de eso, es un mecanismo muy potente que es necesario dominar para códigos mas complejos en Javascript.

About the Author

Mi nombre es Gustavo Hernán Dohara, soy Ingeniero en Sistemas y, sobre todas las cosas, soy un apasionado por la tecnología; tengo más de 10 años de experiencia en diferentes tecnologías y he decidido compartir mis conocimientos y experiencias con cualquiera que desee aprender: alumnos, empleados, freelancers y vos. Al ritmo que cada uno pueda, en el horario en que cada uno pueda y en el lugar que cada uno elija.

 ¡OFERTA! 

 101 Preguntas Sobre javascript

Que te Harán En Cualquier Entrevista (Con Sus Respuestas)