Atajos de JavaScript para principiantes

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(); // '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());  // 0 console.log(count());  // 1 console.log(count());  // 2 

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ón

Cuando 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 llamadas

Cuando 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(); // '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());  // 0 console.log(count());  // 1 console.log(count());  // 2 

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.

Source: https://habr.com/ru/post/es424967/


All Articles