LexicalEnvironment and Closures in EcmaScript

Hello, Habr!



I haven’t written anything for a long time, a lot of work on the project for the last few weeks, but now I have free time, so I decided to introduce you a new article.



Today we will continue to analyze the key concepts of EcmaScript, talk about the Lexical environment and Closure. Understanding the concept of the Lexical environment is very important for understanding closure, and closure is the foundation of so many good techniques and technologies in the JS world (which is based on the EcmaScript specification).



So, let's begin.



Lexical environment (LexicalEnvironment, LO, LE)



The official ES6 specification defines this term as:

Lexical Environment is a type of specification used to resolve identifier names when searching for specific variables and functions based on the lexical structure of ECMAScript code nesting. Lexical Environment consists of a record of the environment and, possibly, a null reference to the external Lexical environment.

Let’s take a closer look.



I will imagine the lexical environment as a kind of structure that stores the connection of context identifiers with their meaning. This is a kind of repository of variables, functions, classes declared in the scope of this context.



Technically, LO is an object with two properties:





Through a link to the parent context of the current context, we can, if necessary, get a link to the "grandfather context" of the parent context, and so on, to the global context, the reference to the parent of which will be null. From this definition it follows that the Lexical environment is the connection of the entity with the contexts that generated it. A kind of ScopeChain in functions is an analog of the Lexical environment. We talked about ScopeChain in detail in this article .



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}
      
      





Technically, the process of resolving identifier names will occur as in ScopeChain, i.e. sequential polling of objects in the LO loop will occur until the desired identifier is found. If the identifier is not found, then ReferenceError.



The lexical environment is created and filled at the stage of creating the context. When the current context finishes executing, it is removed from the call stack, but its Lexical environment can continue to live as long as there is at least one link to it. This is one of the advantages of a modern approach to the design of programming languages. I think it's worth talking about!



Stack Organization vs Dynamically Shared Memory



In stack languages, local variables are stored on the stack, which is replenished when the function is activated, when the function exits, its local variables are removed from the stack.



With a stacked organization, it would not be possible to return a local function from a function, or call a function to a free variable.



A free variable is a variable used by a function, but it is neither a formal parameter nor a local variable for this function.



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





With the stack organization, neither locaVar search in the external LexicalEnvironment nor the return of the innerFn function would be possible, since innerFn is also a local declaration for testFn. Upon completion of testFn, all of its local variables would simply be removed from the stack.



Therefore, another concept was proposed - the concept of dynamically allocated memory (heap, heep) + garbage collector + reference counting. The essence of this concept is simple: as long as there is at least one reference to an object, it is not deleted from memory. More details can be found here .



Closure (Closures)



A closure is a combination of a code block and data of the context in which that block is generated, i.e. it is the relationship of the entity with the generating contexts through a chain of LO or SopeChain.



Let me quote a very good article on this subject:



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





When the person function is executed, JavaScript creates a new execution context and the lexical environment for the function. After this function completes, it will return the displayName function and be assigned to the variable peter.



Thus, her lexical environment will look like this:



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





When the person function completes, its execution context is popped out of the stack. But its lexical environment will still remain in memory, since the lexical environment of its internal displayName function refers to it. Thus, its variables will still be available in memory.



When the peter function is executed (which is actually a reference to the displayName function), JavaScript creates a new execution context and lexical environment for this function.



So his lexical environment will look like this:



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





There is no variable in the displayName function; its environment record will be empty. During the execution of this function, JavaScript will try to find the name variable in the lexical environment of the function.



Since there are no variables in the lexical environment of the displayName function, it will search in the external lexical environment, that is, the lexical environment of the person function, which is still in memory. JavaScript will find this variable and name is printed to the console.

The most important characteristic of a closure in ES is that it uses a static scope (in a number of other languages ​​that use closure, the situation is different).



Example:



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





Another important closure property is the following situation:



 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
      
      





Those. we see that the free variable present in the closures of several functions is changed by reference by them.



Conclusion



In the framework of this article, we briefly described two central concepts for EcmaScript: Lexical environment and Closure. In fact, both of these topics are much broader. If the community wants to get a more in-depth description of the differences between different types of lexical environments or to learn how v8 builds a closure, write about it in the comments.



All Articles