Closures en Javascript

Los Closures permiten a los programadores de JavaScript escribir mejor código. Frecuentemente usamos closures y si no algún día te los encontraras y a simple vista parecen muy complejos pero en este articulo veremos como entenderlos y usarlos en nuestro día a día.

Que es un closure?
Un closure es una función interna que tiene acceso a las variables de otras funciones . El closure tiene tres scope: tiene acceso a su propio scope (las variables que definimos dentro de los corchetes), tiene acceso a las variables de otras funciones, y tiene acceso a las variables globales.

La función interna no solo tiene acceso a las variables sino también a sus parámetros(de la otra función), asi como a las variables y parámetros de la función exterior.

Se crea un Closure agregando una función adentro de otra función.

Ejemplo:

function muestraNombre (nombre, apellido) {
var nombreIntroducido = "Tu nombre es ";
 // Esta función interna tiene acceso a las variables de la exterior, incluyendo el parámetro​​
function muestraNombreCompleto () {
​return nombreIntroducido + nombre + " " + apellido;
}​​
return muestraNombreCompleto ();
}
muestraNombre ("LEHI", "ARTEAGA"); // Tu nombre es LEHI ARTEAGA

Los closures son muy usados en Node.js ya que son asíncronos y tienen arquitectura no bloqueante( non-blocking architecture). Los closures se usan desde librerias viejas como jQuery, un ejemplo:

$(function() {​​
var selecciones = [];
$(".miclase").click(function() {
// este closure tiene acceso a las selecciones 
​selecciones.push (this.prop("nombre"));
​});
​});

La siguiente información es una traducción del articulo https://medium.freecodecamp.com/lets-learn-javascript-closures-66feb44f6a44#.7713f0sdv

Que es un closure?

Los son una extremadamente poderosa propiedad de JavaScript (y la mayoría de lenguajes de programación). Como lo definen en MDN:

Los Closures son funciones que refieren a variables independientes, en otras palabras, la función definida en el closure ‘recuerda’ el entorno en el que fue creado.

Nota: Variables libres son las variables que no son declaradas locamente ni pasadas como parámetro.

Veamos estos ejemplos:

Ejemplo 1:

En el ejemplo de arriba, la función numberGenerator crea una variable local “libre” llamada num (un numero) y checkNumber (una funcion que imprime un numero a la consola). La función checkNumber no tiene variables locales propias — aun así, tiene acceso a las variables dentro de la función exterior numberGenerator, por cause del closure. Entonces, puede ser usada la variable num declarada en numberGenerator para exitosa mente imprimirla en consola, incluso después de que numberGenerator ha retornado(return).

Ejemplo 2:

En este ejemplo demostrare como es que un closure contiene cualquiera de las variables locales que pueden ser declaras en las funciones externas a esta.

Veamos como la variable hello es definida después de la función anónima — pero aun así puede acceder a la variable hello . Esto es por que hello ya ha sido definida en la función “scope” en el tiempo de su creación, haciéndola disponible cuando la función anónima es finalmente ejecutada. (No te preocupes, explicare que significa “scope” mas adelante)

Entendiendo el Alto Nivel (High Level)

Estos ejemplos ilustran lo que los closures son en un alto nivel. El tema general es: tenemos acceso a las variables definidas en las funciones que encubren, incluso después de que las funciones que encubren las cuales definen estas variables han hecho return. Claramente, Algo esta pasando en el fondo que nos permite usar esas variables y hacerlas accesibles después de que la función que cubre ha retornado.

Para entender como esto es posible nos veremos en la necesidad de tocar algunos conceptos relacionados — Empezamos con el overarching context (contexto general) dentro de una función que se ejecuta, conocido como“Execution context”.

Contexto de ejecución

El contexto de ejecución es un concepto abstracto usado por ECMAScript para rastrear la evaluación del tiempo de ejecución del código , Esto puede ser el contexto global en el que el código is ejecutado inicialmente o cuando el flujo de la ejecución entra al cuerpo de una función.

Execution Context

En este punto del tiempo, solo puede haber un contexto de ejecución corriendo, Eso es por que JavaScript es “single threaded,”(de un solo hilo) significando que solo un comando puede ser procesado a la vez. Normalmente, los navegadores mantienen esta ejecucuion usando un “stack.” Un stack es una estructura de datos Last In First Out (LIFO) (Ultimo en entrar Primero en Salir) , osea que la ultima cosa que metamos en nuestro stack es la primer cosa que saldrá de este. (Esto es por que solo podemos insertar o borrar elementos el la parte top(superior) del stack ) El contexto de ejecución presente o “corriendo” siempre es el del top del stack. Siempre se saldrá del top cuando el código en el contexto de ejecución ha sido completamente evaluado, permitiéndonos que el próximo item en el top se convierta en el contexto de ejecución actual.

Ademas, solo por que un contexto de ejecución este corriendo no significa qe tenga que terminar de correr antes de que otro contexto de ejecución pueda empezar. Hay veces que cuando el contexto de ejecución es suspendido y un diferente contexto de ejecución se convierte en el contexto actual ejecutado. El contexto de ejecución suspendido puede entonces tener un punto para regresar a donde se quedo. En cualquier momento un contexto de ejecución es remplazado por otro así un nuevo contexto de ejecución es creado e insertado al stack convirtiéndose en el contexto de ejecución actual.

Para un ejemplo practico de este concepto en acción en el navegador, veamos el ejemplo de abajo:

Entonces cuando boop retorna, se sale del stack y bar continua:

Cuando tenemos muchos contextos de ejecución corriendo uno después de otro — a menudo siendo pausado a la mitad y después reanudado — se necesitara una manera para mantener el rastreo del estado y así podamos manejar el orden y la ejecución de estos contextos. Y ese es de hecho el caso; Según la eespecificacion ECMAScript, cada contexto de ejecución tiene varios componentes de estado que están siendo usados para mantener el registro de el progreso de el código en cada contexto que se ha hecho, esto incluye:

  • Code evaluation state(estado de evaluación del código): Cualquier estado necesitado para ejecutar, suspender y reanudar la evaluación del código asociado con este contexto de ejecución.
  • Function(funcion): El objeto function cuyo contexto de ejecución se esta evaluando (o null si el contexto que se esta siendo evaluado es un script o modulo)
  • Realm: Un set de objetos internos, un ambiente global ECMAScript , todo el código ECMASCRIPT que es cargado dentro de el scope que es el ambiente global , y otros recursos y estados asociados.
  • Lexical Environment: Usado para resolver referencias de identificadores hechas por el código dentro de su contexto de ejecución.
  • Variable Environment: Lexical Environment cuyo EnvironmentRecord mantiene bindings creados por VariableStatements dentro del contexto de ejecución.

Si todo esto suena muy confuso para to, no te preocupes,de todas estas variables, el lexical environmentthis es la mas interesante para nosotros por que resuelve las referencias de identificar“identifier references” hechas por el código dentro de el contexto de ejecución. Puedes pensar en los “identifiers” como variables. Desde nuestra meta original la cual era descubrr como es posible para nosotros que mágicamente accese a las variables incluso después de una función (o “contexto”) ha retornado.

Note: Técnicamente, ambas Variable Environment y Lexical Environment son usadas para implementar closures. Pero por simplicidad, vamos a generalizarlos como un “Environment”. Para una detallada explicación de la diferencia entre ambos puede ver el siguiente articulo por el Dr. Alex Rauschmayer’s excellent article.

Lexical Environment

Por definición: Un Lexical Environment es un tipo de especificación usado para definir la asociación de identificadores para especificar variables y funciones basadas sobre la estructura anidada léxica del código ECMAscript. Un Lexical Environment consiste de un registro de ambiente y una posible referencia nula a otro Lexical Environment externo. Usualmente un Lexical Environment es asociado con alguna estructura sintáctica especifica del código ECMascript tal como FunctionDeclaration, BlockStatement, o un Catch clause de un TryStatement y un nuevo Lexical Environment es creado cada vez que el código es evaluado. — ECMAScript-262/6.0

Veamoslo por partes.

  • “Usado para definir la asociación de identificadores”: El propósito de un Lexical Environment es manejar los datos (ej. identifiers) dentro del código. En otras palabras, da el significado a los identificadores.por ejemplo si tenemos una linea de código “console.log(x / 10)”, no tiene sentido tener una variable (o “identifier”) x sin algo que provea significado para esa variable. El Lexical Environments provee este significado(o “association”) via Environment Record.
  • “Lexical Environment consiste en un Environment Record”: Un Environment Record es una manera de decir que va a mantener un registro de todos los identificadores y sus uniones que existen dentro de un Lexical Environment. Cada Lexical Environment tiene su propio Environment Record.
  • “Estructura anidada Lexical”: Esta es la parte interesante, la cual es básicamente decir que un environment interno referencia a uno externo, y que este environment externo puede tener también su propio environment externo. Como resultado, un environment puede servir como el environment exterior para mas de un environment interno. El global environment es el unico Lexical environment que no tiene un environment exterior. El lenguaje nos puede engañar aquí , para eso usemos una metáfora y pensemos en los lexical environments como capas de una cebolla: el global environment es la capa exterior de toda la cebolla; y cada capa interna es una anidada a este.
Source: http://4.bp.blogspot.com/

Abstractamente, el environment se ve como en este pseudocodigo :

  • “Un nuevo Lexical Environment es creado cada vez que tal código es evaluado”: Cada vez que una función externa es llamada, un nuevo lexical environment es creado. Esto es importante — Veremos como regresar a este punto al final. ( nota: una función no es la única manera de crear un Lexical Environment. Otras incluyen un block statement o un catch clause. por simplicidad nos enfocaremos en el environment credo por funcionest)

En resumen, cada contexto de ejecución tiene un Lexical Environment. Este Lexical environments mantiene variables y sus valores asociados, y también la referencia hacia su environment exterior. El Lexical Environment puede ser el global environment, un module environment (que contiene las uniones para el las declaraciones top de un modulo), o una función environment (environment creado debido a la invocación de una función).

Scope Chain

Basado en las definiciones de arriba, sabemos que un environment tiene accesos a environment padre, y que a su vez el environment padre tiene acceso a su padres. Este set de identifiers que cada environment tiene acceso es llamado “scope.” Podemos anidar scopes dentro de una cadena jerárquica de environments conocida como “scope chain”.

Veamos un ejemplo de esta estructura anidada:

Como puedes ver, bar esta anidada dentro de foo. Para ayudar a visualizar el anidamiento, veamos el siguiente diagrama:

Este scope chain, o cadena de environments asociadas con una función, es guardada a la función objeto al tiempo de su creación. En otras palabras, Es definida estéticamente por locación dentó del código fuente. (Esto también es conocido como “lexical scoping”.)

Tomemos un desvió rápido para entender la diferencia entre “dynamic scope” y “static scope”, el cual ayudara a clarificar por que el static scope (o lexical scope) es necesario para tener closures.

Dynamic Scope vs. Static Scope

Los lenguajes con Dynamic scoped tienen “stack-based implementations”, Esto significa que las variables ocales y argumentos de las funciones son guardadas en un stack. Por lo cual, el stack del estado del tiempo de ejecución del programa determina a que variable estas haciendo referencia.

por el otro lado, static scope es cuando las variables referenciadas en un contexto son guardadas en e tiempo de creación. En otras palabras, la estructura de el código fuente del programa determina a que variables te estas refiriendo.

En este punto, tal vez te estés preguntando como dynamic scope y static scope son diferentes. Aquí hay dos ejemplos para ilustrarlo:

Ejemplo 1:

Vemos aquí que el static scope y dynamic scope retornan diferentes valores cuando la función es invocada.

Con static scope, el valor de retorno de bar esta basado en el valor de x al momentos de creación de foo. Esto es por el static y lexical structure de el código fuente, lo que resulta en x siendo 10 y el resultado siendo 15.

Dynamic scope, por otro lado, nos da un stack de definiciones de variable rastreadas en la ejecución — tal que la x que usamos depende en exactamente esta en el scope y ha sido definida dinamicamente en la ejecución. Corriendo la función bar agrega x = 2 hacia el top de el stack, haciendo foo retornar 7.

Ejemplo 2:

De manera similar, en el ejemplo de arriba la variable myVar es resuelta usando el valor de myVar en el lugar en donde la función es llamada. Static scope, por otro lado, resuelve myVar a la variable que ha sido guardada en el scope de dos IIFE functions en su creación.

Como puedes ver, dynamic scope aveces se puede entender de varias maneras. No esta exactamente claro de cual scope sera resuelta la variable free.

Closures

Hemos cubierto todo lo necesario para entender closures:

Cada función tiene un contexto de ejecución, el cual comprende de un environment el cual da el significado a las variables dentro de esa función y hace referencia al environment padre. Una referencia al environment padre hace que todas las variables en el parent scope estén disponibles para todas las funciones internas, sin importar si llamamos a las funciones internas fuera o dentro del scope en el cual fueron creadas.
Entonces ,parece que la función “recuerda” este environment (o scope) por que la función literalmente hace referencia a el (y las variables definidas en ese environment)!

Regresando a las estructuras anidadas:

Basado en nuestro entendimiento de como los environments funcionan, podemos decir que las environment definitions para el ejemplo de arriba deben verse algo así (nota, esto es solo pseudocodigo):

Cuando invocamos la función test, obtenemos 45, el cual es el valor de retorno de invocar la función bar (por que foo retorna a bar). bar tiene acceso a las variables libres e incluso después de que la función foo ha retornado por que bar tiene una referencia a y mediante su environment exterior, el cual es foo’s environment! bar también tiene acceso a la variable global x debido a que foo’s environment tiene acceso al global environment. Esto es llamado “scope-chain lookup.”

Regresando a nuestra discusion de dynamic scope vs static scope: para que los closures sean implementados, no podemos usar dynamic scoping vía un dynamic stack para guardar nuestras variables. La razón es por que esto significaría que cuando la funciona retorna, las variables van a ser sacadas del stack y no estarán ya disponibles — lo cual contradice nuestra definición inicial de closure. Lo que pasa es que los datos del closure de el contexto padre son guardados en lo que se llama “heap,” el cual permite a los datos persistir incluso después de la llamada de la función que los hace retornar

tiene sentido? Bien! Ahora que entendemos las partes internas en nivel abstracto, veamos otros ejemplos:

ejemplo 1:

Un gran ejemplo/error is es cuando hay un ciclo for y tratamos de asociar el contador en el ciclo for con alguna función en el ciclo for:

Regresando a lo que acabamos de aprender,es muy fácil detectar el error aquí! Abstractamente, aquí esta como el environment se vería cuando el ciclo for termina:

La idea incorrecta aquí fue que el scope es diferente cada cada una de las cinco funciones dentro del result array. envés de eso, lo que realmente estaba pasando es que el environment (o context/scope) es el mismo para todas las cinco funciones dentro del result array. Por lo tanto, cada vez que la variable i es incrementada, se actualiza el scope — el cual es compartido por todas las funciones . Es por eso que ninguna de las 5 funciones que están tratando de accesar i retorna 5 (i es igual a 5 cuando el ciclo for existe).

Una manera de arreglar esto es crear un contexto adicional que envuelva a cada función, para que cada una tenga su propio context/scope:

Otro, gran enfoque es usar let envés de var, ya que let es block-scoped y entonces un nuevo identificador se unirá para cada iteracion en el ciclo for:

Ejemplo 2:

En este ejemplo, mostraremos como cada llamada a una funcion, crea un nuevo closure:

En este ejemplo, podemos ver que cada llama a la función iCantThinkOfAName crea un nuevo closure, llamado foo y bar. las invocaciones subsecuentes para cada función de closure actualizan las variables del closure dentro del mismo closure, demostrando que las variables en cada closure continúan siendo usables por iCantThinkOfAName’s doSomething después de que iCantThinkOfAName retorna.

Ejemplo 3:

Lo que podemos observar es que mysteriousCalculator es en el scope global, y este retorna dos funciones. Abstracta mente, los environments para el ejemplo se deben ver así:

Debido a que nuestras funciones add y subtract tienen una referencia a el environment de la función mysteriousCalculator , estas pueden hacer uso de las variables en ese environment para calcular el resultado.

Ejemplo 4:

Un ejemplo final para demostrar la importancia del uso de closures: para mantener una referencia privada a la variable in el scope externo.

Esta es una tecnica muy poderosa — nos da acceso exclusivo a la variable password de la funcion cosure llamada guessPassword , mientras que hacendo imposible el acceso a password desde fuera.

Tl;dr

  • El contexto de ejecución es un concepto abstracto usado por la especifican de ECMAScript para llevar registro de la evaluación del código ejecutado. En cualquier punto del tiempo, solo puede haber un contexto de ejecución que esta ejecutando código.
  • Cada contexto de ejecución tiene un Lexical Environment. Este Lexical environments mantiene las uniones de identificadores (ej. variables y sus valores asociados), y también tienen una referencia a su environment externo.
  • El set de identificadores que cada environment tiene acceso a su llamado “scope.” Podemos anidar estos scopes en una cadena jerárquica de environments, conocida como “scope chain”.
  • Cada función tiene un contexto de ejecución, el cual comprende de un Lexical Environment el cual da significado a las variables dentro de la función y la referencia a su environment padre. También pareciera como si la función recordara su environment (o scope) por que la función literalmente tiene una referencia a su environment. Esto es un closure.
  • Un closure es creado cada vez que una función exterior es llamada, en otras palabras, la función interna no tiene la necesidad de retornar para que un closure sea creado.
  • El scope de un closure en JavaScript es léxico, significa que es definido estaticamente por la locación del código fuente.
  • Los Closures tiene muchos casos prácticos. Un caso importante de uso es mantener una referencia privada a una variable en el scope externo.

Otras lecturas

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.