Entorno y cierres léxicos en EcmaScript

Hola Habr!

No he escrito nada durante mucho tiempo, mucho trabajo en el proyecto durante las últimas semanas, pero ahora tengo tiempo libre, así que decidí presentarte un nuevo artículo.

Hoy continuaremos analizando conceptos clave de EcmaScript, hablando sobre el entorno léxico y el cierre. Comprender el concepto de entorno léxico es muy importante para comprender el cierre, y el cierre es la base de tantas buenas técnicas y tecnologías en el mundo JS (que se basa en la especificación EcmaScript).

Entonces comencemos.

Entorno léxico (entorno léxico, LO, LE)


La especificación oficial ES6 define este término como:
El entorno léxico es un tipo de especificación utilizada para resolver nombres de identificadores cuando se buscan variables y funciones específicas basadas en la estructura léxica de la anidación del código ECMAScript. El entorno léxico consiste en un registro del entorno y, posiblemente, una referencia nula al entorno léxico externo.
Echemos un vistazo más de cerca.

Imaginaré el entorno léxico como un tipo de estructura que almacena la conexión de identificadores de contexto con su significado. Este es un tipo de repositorio de variables, funciones, clases declaradas en el ámbito de este contexto.

Técnicamente, LO es un objeto con dos propiedades:

  • registro de entorno (aquí es donde se almacenan todos los anuncios)
  • enlace al contexto generativo LO.

A través de un enlace al contexto padre del contexto actual, podemos, si es necesario, obtener un enlace al "contexto de abuelo" del contexto padre, y así sucesivamente, al contexto global, cuya referencia al padre será nula. De esta definición se deduce que el entorno léxico es la conexión de la entidad con los contextos de su origen. Un tipo de ScopeChain en funciones es un análogo del entorno léxico. Hablamos de ScopeChain en detalle en este artículo .

let x = 10; let y = 20; const foo = z => { let x = 100; return x + y + z; } foo(30);// 150.   foo    {record: {z: 30, x: 100}, parent: __parent}; // __parent      {record: {x: 10, y: 20}, parent: null} 

Técnicamente, el proceso de resolución de nombres de identificadores ocurrirá como en ScopeChain, es decir. el sondeo secuencial de objetos en el bucle LO ocurrirá hasta que se encuentre el identificador deseado. Si no se encuentra el identificador, entonces ReferenceError.

El entorno léxico se crea y se llena en la etapa de creación del contexto. Cuando el contexto actual termina de ejecutarse, se elimina de la pila de llamadas, pero su entorno léxico puede continuar vivo mientras exista al menos un enlace. Esta es una de las ventajas de un enfoque moderno para el diseño de lenguajes de programación. ¡Creo que vale la pena hablar!

Organización de la pila frente a memoria compartida dinámicamente


En los idiomas de la pila, las variables locales se almacenan en la pila, que se repone cuando se activa la función, cuando la función sale, sus variables locales se eliminan de la pila.

Con una organización apilada, no sería posible devolver una función local de una función o llamar una función a una variable libre.

Una variable libre es una variable utilizada por una función, pero no es un parámetro formal ni una variable local para esta función.

 function testFn() { var locaVar = 10; //     innerFn function innerFn(p) { alert(p + localVar); } return innerFn; //  } var test = testFn();//  innerFn   test();//       

Con la organización de la pila, no sería posible la búsqueda locaVar en el entorno externo de Lexical ni el retorno de la función innerFn, ya que innerFn es también una declaración local para testFn. Al completar testFn, todas sus variables locales simplemente se eliminarían de la pila.

Por lo tanto, se propuso otro concepto: el concepto de memoria asignada dinámicamente (montón, heep) + recolector de basura + conteo de referencias. La esencia de este concepto es simple: siempre que haya al menos una referencia a un objeto, no se eliminará de la memoria. Más detalles se pueden encontrar aquí .

Cierre (cierres)


Un cierre es una combinación de un bloque de código y datos del contexto en el que se genera ese bloque, es decir. es la relación de la entidad con los contextos generadores a través de una cadena de LO o SopeChain.

Permítanme citar un muy buen artículo sobre este tema:

 function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter(); // prints 'Peter' 

Cuando se ejecuta la función persona, JavaScript crea un nuevo contexto de ejecución y el entorno léxico para la función. Una vez completada esta función, devolverá la función displayName y se asignará a la variable peter.

Por lo tanto, su entorno léxico se verá así:

 personLexicalEnvironment = { environmentRecord: { name : 'Peter', displayName: < displayName function reference> } outer: <globalLexicalEnvironment> } 

Cuando la función de persona se completa, su contexto de ejecución se elimina de la pila. Pero su entorno léxico aún permanecerá en la memoria, ya que el entorno léxico de su función de referencia interna se refiere a él. Por lo tanto, sus variables aún estarán disponibles en la memoria.

Cuando se ejecuta la función peter (que en realidad es una referencia a la función displayName), JavaScript crea un nuevo contexto de ejecución y un entorno léxico para esta función.

Entonces su entorno léxico se verá así:

 displayNameLexicalEnvironment = { environmentRecord: { } outer: <personLexicalEnvironment> } 

No hay ninguna variable en la función displayName; su registro de entorno estará vacío. Durante la ejecución de esta función, JavaScript intentará encontrar la variable de nombre en el entorno léxico de la función.

Como no hay variables en el entorno léxico de la función displayName, buscará en el entorno léxico externo, es decir, el entorno léxico de la función persona, que todavía está en la memoria. JavaScript encontrará esta variable y el nombre se imprimirá en la consola.
La característica más importante de un cierre en ES es que usa un alcance estático (en varios otros idiomas que usan cierre, la situación es diferente).

Un ejemplo:

 var a = 5; function testFn() { alert(a); } (function(funArg) { var a = 20; funArg();//  5 ..  ScopeChain/LexicalEnvironment testFn   ,    = 5 })(testFn) 

Otra propiedad importante de cierre es la siguiente situación:

 var first; var second; function testFn() { var a = 10; first = function() { return ++a; } second = function() { return --a; } a = 2; first();//3 } testFn(); first();//4 second();//3 

Es decir vemos que la variable libre presente en los cierres de varias funciones es cambiada por referencia por ellos.

Conclusión


En el marco de este artículo, describimos brevemente dos conceptos centrales para EcmaScript: entorno léxico y cierre. De hecho, ambos temas son mucho más amplios. Si la comunidad quiere obtener una descripción más detallada de las diferencias entre los diferentes tipos de entornos léxicos o aprender cómo v8 construye un cierre, escríbalo en los comentarios.

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


All Articles