Introducción a los “closures” en Javascript

Los “closures” (“cierre” o “clausura” en español) son funciones que se refieren a variables independientes. No te preocupes si esto suena confuso en este momento, a lo largo del artículo cobrará sentido y quedará más claro.

Para explicarlo de forma más sencilla, los closures pueden considerarse un tipo especial de objeto, que incluye una función y el entorno (o variables) en el que se creó esa función. Los closures almacenan el ambiente en el que fueron creados. En este artículo hablaremos un poco más de sus características, su implementación y algunos errores que hay que evitar al utilizarlos.

Funciones

Para que podamos entender bien lo que es un closure, primero hablemos de lo que es una función en Javascript. Algunas de las características más importantes de las funciones es que son objetos, cuyo constructor es Function. Estos pueden ser almacenados en variables y estructuras de datos (por ejemplo, let a = function (b){…} ). También pueden utilizarse como parámetros de una función o ser retornadas como resultado de una función (por ejemplo, los callbacks). O pueden ser anónimas (por ejemplo, (function (){….})(); ).
Habiendo puesto en claro el concepto de función, hablemos ahora de lo que es el scope, también conocido como ámbito o alcance, que es otra de las palabras claves para entender lo que es un closure.

Scope

El scope se refiere a, en términos básicos, hasta donde será visible la variable que estoy declarando. En el caso de Javascript, el scope se extiende a la función dentro de la cual fue declarada. No importa si la estoy declarando dentro de un bloque if, for o while. Esto se conoce como function-level scope, a diferencia de otros lenguajes, como Java, que tienen un block-level scope.

Con estas dos definiciones, estamos listos para entrar en materia y profundizar en lo que es un closure.

Closures

Ahora podemos definir los closures como una técnica de encapsulamiento, basada en el anidamiento de funciones que permite crear privacidad. ¿Para qué? Pues de este modo, el scope de una función interna contiene o hereda el scope de una función padre, incluso si esta función padre ya ha retornado, pues conservamos dicho scope en caché, dentro del closure.
Veamos un ejemplo con el que esto quedará mucho más claro:

Supongamos que queremos implementar un simple contador en nuestro sistema:

Fuente: 2

Este esquema, aunque funciona, presenta un problema de seguridad, ya que el contador puede ser afectado desde afuera de la función add, debido al scope. Ahora, podemos intentar incluir la declaración del counter dentro de la función add de esta manera:

Fuente: 2

Pero en este caso, cada vez que llamemos a la función add, el counter se iniciará a cero y obtendremos siempre 1 como resultado. Quizá podamos entonces usar una función anidada y aprovechar las propiedades del scope, de esta manera:

Fuente: 2

Esto sería genial, pero no podemos acceder a la función plus desde fuera de la función add, así que la solución no nos funcionará. Ahora, ¿Cómo nos puede ayudar un closure?

Fuente: 2

Vemos que add es una variable que contiene una función anónima, donde iniciamos el counter a 0, y regresamos otra función anónima, donde se añade 1 al counter.

Nuestra función anónima, autoinvocada (notemos que escribimos (); al final de ella) se ejecutará una sola vez, así que nuestro counter será iniciado a cero sólo una vez. Así, cada vez que invocamos add, se regresará como resultado el counter mas 1, que es justo lo que queremos. Además, de este modo, el counter no puede ser modificado desde fuera de la variable add.

El sitio W3school (https://www.w3schools.com/) tiene un excelente ejemplo que puedes probar aqui.

Errores comunes

Un error muy común es crear un closure dentro de un ciclo. ¿Por qué es un error? Porque aunque se crea un closure en cada paso del ciclo, de hecho estarían utilizando el mismo scope, y cuando llamemos al closure, el ciclo habrá terminado y el último ciclo será el scope que tengamos almacenado en cada closure creado. Además ésto nos puede crear problemas de desempeño, en términos de memoria y velocidad de procesamiento.

Conclusiones

Los closures son una herramienta poderosa, porque nos permiten asociar datos con una función, y esto nos permite definir eventos y asociar respuestas a esos eventos basados en el status actual de nuestra aplicación. Sin embargo, hay que ser cuidadosos y entender bien tanto su implementación como los recursos que consumiremos al utilizarlos.

Referencias

  1. MDN / Closures. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
  2. W3Schools / Javascript Closures. https://www.w3schools.com/js/js_function_closures.asp
  3. Óscar Sotorrío / Closures en JavaScript: entiéndelos de una vez. http://www.variablenotfound.com/2012/10/closures-en-javascript-entiendelos-de.html
  4. Closures en Javascript. https://jherax.wordpress.com/2015/02/13/javascript-closures/