Los cierres son uno de los conceptos fundamentales de JavaScript, que causan dificultades a muchos principiantes, que todo programador de JS debe conocer y comprender. Al tener una buena comprensión de los cierres, puede escribir un código mejor, más eficiente y más limpio. Y esto, a su vez, contribuirá a su crecimiento profesional.
El material, cuya traducción publicamos hoy, está dedicado a la historia de los mecanismos internos de los cierres y cómo funcionan en los programas JavaScript.
¿Qué es un cierre?
Un cierre es una función que tiene acceso a un ámbito formado por una función externa en relación con él, incluso después de que esta función externa haya completado su trabajo. Esto significa que un cierre puede almacenar variables declaradas en una función externa y argumentos pasados a ella. Antes de proceder, de hecho, a los cierres, trataremos el concepto de "entorno léxico".
¿Qué es un entorno léxico?
El término "entorno léxico" o "entorno estático" en JavaScript se refiere a la capacidad de acceder a variables, funciones y objetos en función de su ubicación física en el código fuente. Considere un ejemplo:
let a = 'global';  function outer() {    let b = 'outer';    function inner() {      let c = 'inner'      console.log(c);   // 'inner'      console.log(b);   // 'outer'      console.log(a);   // 'global'    }    console.log(a);     // 'global'    console.log(b);     // 'outer'    inner();  } outer(); console.log(a);         // 'global' 
Aquí, la función 
inner() tiene acceso a variables declaradas en su propio ámbito, en el ámbito de la función 
outer() y en el ámbito global. La función 
outer() tiene acceso a las variables declaradas en su propio ámbito y en el ámbito global.
La cadena de alcance del código anterior se verá así:
 Global { outer {   inner } } 
Tenga en cuenta que la función 
inner() está rodeada por el entorno léxico de la función 
outer() , que a su vez está rodeada por un ámbito global. Es por eso que la función 
inner() puede acceder a las variables declaradas en la función 
outer() y en el ámbito global.
Ejemplos prácticos de cierres.
Considere, antes de desarmar las complejidades de los circuitos internos, algunos ejemplos prácticos.
▍ Ejemplo No. 1
 function person() { let name = 'Peter'; return function displayName() {   console.log(name); }; } let peter = person(); peter();  
Aquí llamamos a la función 
person() , que devuelve la función interna 
displayName() , y almacenamos esta función en la variable 
peter . Cuando, después de esto, llamamos a la función 
peter() (la variable correspondiente en realidad almacena una referencia a la función 
displayName() ), el nombre 
Peter se muestra en la consola.
Al mismo tiempo, no hay una variable 
displayName() en la función 
displayName() , por lo que podemos concluir que esta función puede acceder de alguna manera a la variable declarada en la función externa a ella, 
person() , incluso después de eso cómo funcionó esta función. Quizás esto se deba a que la función 
displayName() es en realidad un cierre.
▍ Ejemplo No. 2
 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());   
Aquí, como en el ejemplo anterior, almacenamos el enlace a la función interna anónima devuelta por la función 
getCounter() en el 
count variable. Como la función 
count() es un cierre, puede acceder a la variable de 
counter de la función 
getCount() incluso después de que la función 
getCounter() haya completado su trabajo.
Tenga en cuenta que el valor de la variable del 
counter no se restablece a 0 cada vez que se llama a la función 
count() . Puede parecer que debería restablecerse a 0, como lo sería cuando se llama a una función regular, pero esto no sucede.
Esto funciona así porque cada vez que se llama a la función 
count() , se crea un nuevo ámbito para ella, pero solo hay un ámbito para la función 
getCounter() . Dado que la variable de 
counter se declara en el alcance de la función 
getCounter() , su valor entre llamadas a la función 
count() se guarda sin restablecer a 0.
¿Cómo funcionan los cortocircuitos?
Hasta ahora, hemos hablado sobre qué son los cierres y hemos examinado ejemplos prácticos. Ahora hablemos de los mecanismos internos de JavaScript que los hacen funcionar.
Para comprender los cierres, debemos tratar con dos conceptos cruciales de JavaScript. Este es el contexto de ejecución y el entorno léxico.
▍ Contexto de ejecución
El contexto de ejecución es un entorno abstracto en el que se calcula y ejecuta el código JavaScript. Cuando se ejecuta el código global, esto sucede dentro del contexto de ejecución global. El código de función se ejecuta dentro del contexto de la ejecución de la función.
En algún momento, el código puede ejecutarse en un solo contexto de ejecución (JavaScript es un lenguaje de programación de un solo subproceso). Estos procesos se gestionan utilizando la llamada Pila de llamadas.
La pila de llamadas es una estructura de datos organizada de acuerdo con el principio LIFO (Última entrada, Primera salida - Última entrada, Primera salida). Los nuevos elementos solo se pueden colocar en la parte superior de la pila, y solo los elementos se pueden eliminar de ella.
El contexto de ejecución actual siempre estará en la parte superior de la pila, y cuando la función actual salga, su contexto de ejecución se extrae de la pila y el control se transfiere al contexto de ejecución, que se encuentra debajo del contexto de esta función en la pila de llamadas.
Considere el siguiente ejemplo para comprender mejor cuál es el contexto de ejecución y la pila de llamadas:
Ejemplo de contexto de ejecuciónCuando se ejecuta este código, el motor de JavaScript crea un contexto de ejecución global para ejecutar el código global, y cuando encuentra una llamada a la 
first() función 
first() , crea un nuevo contexto de ejecución para esta función y lo coloca en la parte superior de la pila.
La pila de llamadas de este código se ve así:
Pila de llamadasCuando se completa la ejecución de la 
first() función 
first() , su contexto de ejecución se recupera de la pila de llamadas y el control se transfiere al contexto de ejecución que se encuentra debajo, es decir, al contexto global. Después de eso, se ejecutará el código restante en el ámbito global.
▍ Ambiente léxico
Cada vez que el motor JS crea un contexto de ejecución para ejecutar una función o código global, también crea un nuevo entorno léxico para almacenar las variables declaradas en esta función durante su ejecución.
El entorno léxico es una estructura de datos que almacena información sobre la correspondencia de identificadores y variables. Aquí, "identificador" es el nombre de una variable o función, y "variable" es una referencia a un objeto (esto incluye funciones) o un valor de un tipo primitivo.
El entorno léxico contiene dos componentes:
- Un registro de entorno es el lugar donde se almacenan las declaraciones de variables y funciones.
- Referencia al entorno externo: un enlace que le permite acceder al entorno léxico externo (principal). Este es el componente más importante que debe tratarse para comprender los cierres.
Conceptualmente, el entorno léxico se ve así:
 lexicalEnvironment = { environmentRecord: {   <identifier> : <value>,   <identifier> : <value> } outer: < Reference to the parent lexical environment> } 
Eche un vistazo al siguiente fragmento de código:
 let a = 'Hello World!'; function first() { let b = 25;  console.log('Inside first function'); } first(); console.log('Inside global execution context'); 
Cuando el motor JS crea un contexto de ejecución global para ejecutar código global, también crea un nuevo entorno léxico para almacenar variables y funciones declaradas en el ámbito global. Como resultado, el entorno léxico del alcance global se verá así:
 globalLexicalEnvironment = { environmentRecord: {     a : 'Hello World!',     first : < reference to function object > } outer: null } 
Tenga en cuenta que la referencia al entorno léxico externo ( 
outer ) se establece en 
null , ya que el ámbito global no tiene un entorno léxico externo.
Cuando el motor crea un contexto de ejecución para la 
first() función 
first() , también crea un entorno léxico para almacenar las variables declaradas en esta función durante su ejecución. Como resultado, el entorno léxico de la función se verá así:
 functionLexicalEnvironment = { environmentRecord: {     b : 25, } outer: <globalLexicalEnvironment> } 
El enlace al entorno léxico externo de la función se establece en 
<globalLexicalEnvironment> , ya que en el código fuente el código de la función está en el ámbito global.
Tenga en cuenta que cuando la función termina su trabajo, su contexto de ejecución se recupera de la pila de llamadas, pero su entorno léxico puede eliminarse de la memoria o puede permanecer allí. Depende de si en otros entornos léxicos hay referencias a este entorno léxico en forma de enlaces a un entorno léxico externo.
Análisis detallado de ejemplos de trabajo con cierres.
Ahora que nos hemos armado con el conocimiento del contexto de ejecución y el entorno léxico, volveremos a los cierres y analizaremos con mayor profundidad los mismos fragmentos de código que ya examinamos.
▍ Ejemplo No. 1
Eche un vistazo a este fragmento de código:
 function person() { let name = 'Peter'; return function displayName() {   console.log(name); }; } let peter = person(); peter();  
Cuando se ejecuta la función 
person() , el motor JS crea un nuevo contexto de ejecución y un nuevo entorno léxico para esta función. Al finalizar el trabajo, la función devuelve la función 
displayName() , una referencia a esta función se escribe en la variable 
peter .
Su entorno léxico se verá así:
 personLexicalEnvironment = { environmentRecord: {   name : 'Peter',   displayName: < displayName function reference> } outer: <globalLexicalEnvironment> } 
Cuando la función 
person() sale, su contexto de ejecución se extrae de la pila. Pero su entorno léxico permanece en la memoria, ya que hay un enlace a él en el entorno léxico de su función interna 
displayName() . Como resultado, las variables declaradas en este entorno léxico permanecen disponibles.
Cuando se llama a la función 
peter() (la variable correspondiente almacena una referencia a la función 
displayName() ), el motor JS crea un nuevo contexto de ejecución y un nuevo entorno léxico para esta función. Este entorno léxico se verá así:
 displayNameLexicalEnvironment = { environmentRecord: {   } outer: <personLexicalEnvironment> } 
No hay variables en la función 
displayName() , por lo que su registro de entorno estará vacío. Durante la ejecución de esta función, el motor JS intentará encontrar la variable de 
name en el entorno léxico de la función.
Como la búsqueda no se puede encontrar en el entorno léxico de la función 
displayName() , la búsqueda continuará en el entorno léxico externo, es decir, en el entorno léxico de la función 
person() , que todavía está en la memoria. Allí, el motor encuentra la variable deseada y muestra su valor en la consola.
▍ Ejemplo No. 2
 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());   
El entorno léxico de la función 
getCounter() se verá así:
 getCounterLexicalEnvironment = { environmentRecord: {   counter: 0,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 
Esta función devuelve una función anónima que se asigna a la variable de 
count .
Cuando se ejecuta la función 
count() , su entorno léxico se ve así:
 countLexicalEnvironment = { environmentRecord: { } outer: <getCountLexicalEnvironment> } 
Al realizar esta función, el sistema buscará la variable de 
counter en su entorno léxico. En este caso, nuevamente, el registro del entorno de la función está vacío, por lo que la búsqueda de la variable continúa en el entorno léxico externo de la función.
El motor encuentra la variable, la muestra en la consola e incrementa la variable del 
counter , que se almacena en el entorno léxico de la función 
getCounter() .
Como resultado, el entorno léxico de la función 
getCounter() después de la primera llamada a la función 
count() se verá así:
 getCounterLexicalEnvironment = { environmentRecord: {   counter: 1,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 
Cada vez que se llama a la función 
count() , el motor de JavaScript crea un nuevo entorno léxico para esta función e incrementa la variable del 
counter , lo que conduce a cambios en el entorno léxico de la función 
getCounter() .
Resumen
En este artículo, hablamos sobre qué son los cierres y clasificamos los mecanismos de JavaScript subyacentes. Los cierres son uno de los conceptos fundamentales de JavaScript más importantes, y todos los desarrolladores de JS deberían comprenderlos. Comprender los cierres es uno de los pasos para escribir aplicaciones efectivas y de alta calidad.
Estimados lectores! Si tiene experiencia en el desarrollo de JS, comparta ejemplos prácticos de uso de cierres con principiantes.
