“Basic” JavaScript (Part 2): Lexical Environment – Something you need to know about Closures

Tram Ho

In the previous section we looked at some of the basic components of the Complier suite of JavaScript. In the previous post, there is a concept of Lexical Environment that I have not had time to explain, so in this article we will learn more about this concept and how it relates to its Closures. JavaScript like.

Before reading this article, if you do not know what Closures are, you should take some time to google the basics of Closures.

Closures can be a difficult concept when you are new to the JavaScript universe . You can read many different definitions of Closures on the internet. But as you can see most of these definitions are ambiguous, confusing and inexplicable the underlying cause of the existence and the purpose of the Closures.

In this article we will try to clear up some concepts of ECMAScript 262, including Execution Context , Lexical Environment , and Identifier Resolution . Also, we will know that due to the above mechanisms, all functions in ECMAScript are Closures .

Execution Context

Let’s repeat this concept a bit. The JavaScript compiler creates a new context whatever it is about to execute a pre-written function or script. Every script / piece of code starts with an Execution Context called the Global Execution Context . And every time we call a function, a new Execution Context is created and placed on top of the Excution stack . The same happens when you call a function nested within another.

Let’s see what happens when our code is executed as shown in the picture above:

  • A Global Execution Context is created and put to the bottom of the Excution stack
  • When the bar () is called, a bar Execution Context will be created and placed on top of the Global Execution Context . In the previous article we knew that each function when called generates an Execution Context with its unique identifier, in this case function bar () => bar Execution Context.
  • Then, when bar () calls to nested foo () function, a foo Execution Context will be created and placed on top of the Execution Context bar.
  • When foo () return – ie function foo has finished executing, foo Execution Context will be removed from the stack and the thread will return to the Execution Context bar.
  • When bar () execution ends, the thread will return to the Global Execution Context , and finally the stack will be emptied.

The Excution stack is executed according to LIFO (Last In First Out) structure, it waits for the execution of the topmost context to complete before executing when the underlying context.

Conceptually, the Execution context is structured as follows:

Don’t worry if this structure is frightening. We will look at its components in a moment. The bottom line to remember here is that every call to the Execution context will have 2 states – corresponding to 2 phases:

  • Creation Stage ( Creation Phrase )
  • Execution StageExecution Stage (corresponding to Execution Phrase )

The Initialization phase is when the context has been created but not yet called. Some of the following happen during the initialization phase:

  • The VariableEnvironment is used to store the starting values ​​for variables, arguments, and function declarations. The declared var variables will be initialized with a value of undefined
  • The value of this is determined
  • The LexicalEnvironment is only a copy of the VariableEnvironment during this period

Now let’s find out what Lexical Environment is.

Lexical Environment

According to ECMAScript 262 (8.1):

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code

Lexical Environment is a special format used to define the relationship between an identifier (variable name, function name) and its corresponding value, based on the nesting structure of ES.

Let’s take a look at a few things here. A Lexical Environment consists of two main components: the environment record and a reference to the external Lexical Environment (the father of the current Lexical Environment):

Visually it should look like this:

As you can see when you want the identifier “x” in the foo context, you need to have access to the global environment. This is called Identifier Resolution and it happens on the execution context running.

Now, based on this knowledge of this Environment, let’s get back to the structure of the Execution context and see what’s happening in there:

  • VariableEnvironment : Its environment is used to store initialization values ​​for variables, arguments, and function declarations. These values ​​will be assigned to their actual values ​​when entering the activation phase.

  • LexicalEnvironment : Initially it was just a copy of the VariableEnvironment. In the running context, it is used to determine the constraint of an identifier (eg a variable) appearing in the context.

Both the VariableEnvironment (VE) and LexicalEnvironment (LE) are essentially Lexical Environment, which means that both essentially (at the initialization stage) statically store external constraints to use for functions created inside. in context. This involves Closures .

Storing external static links for internal functions contributes to the formation of Closures.

Identifier Resolution aka Scope chain lookup

Before we get into Closures, let’s learn a little bit about how the Scope string is created in the Execution context. As we saw earlier, each Execution context has a LexicalEnvironment used to resolve identifiers. All local constraints for the context are stored in the Environment records table. If the identifier cannot be found in the current environmentRecord, the identifier will continue to find the Environment records table in the external environment (parent context). This process will continue until the identifier has received a value. If no match is found, a ReferenceError will appear.

Now, the thing to remember here is that the LexicalEnvironment statically stores a static link to the external environment during the Context Initialization phase and will use it during the run of the context.

Closures

As we saw in the previous section that at the initialization stage, the static storage linked to the LexicalEnvironment’s external environment inside will involve Closures regardless of whether a function is activated or not. Let’s look at an example:

Example 1

The foo’s LexicalEnvironment binds to “a” at the time of initialization, which now has a value of 10. So when foo is then called (the execution phase), the value of “a” is 10. not 20.

Conceptually, the above example identity resolution process will look like this:

Since foo’s Reference links to the Environment of the Global context – where “a” is being stored is 10, so the value “a” in foo will be 10.

Example 2

When the outer () return function, its Execution context is dropped from the Execution stack. But when we call the innerFunc () function later, it still manages to print out the correct value because the LexicalEnvironment of the function inside it statically stored the value of the “id” constraint of the outer environment (funciton outer). since it was created.

Here we can realize, although the context of outer has been removed from the Execution stack, however the Reference to the LexicalEnvironment outer of the innerFunc () function is still preserved without being lost. This is exactly the meaning of static storage that we have talked about a lot in the article.

summary

  • The Execution context stack has a LIFO structure
  • There is a total global context where our code is executed
  • Each call to a function creates a new Execution context . If it has a nested function in which it is called, a new execution context will continue to be created and placed above the parent context. When the context is executed, it is removed from the stack and the thread goes back to the next context in the stack.
  • Lexical Environment has two components: environmentRecord and a reference to the external environment.
  • The VariableEnvironment (VE) and LexicalEnvironment (LE) both statically store constraints to the external environment for use by functions within its own context.
  • All functions at the Initialization Stage statically store the associations with their parent environment. This allows nested functions to access the outer join even when the parent context is removed from the Execution stack. This mechanism is the foundation of Closures in JavaScript.
Share the news now

Source : Viblo