📜 ⬆️ ⬇️

What are you, a closure in javascript?

In this article I will try to disassemble the mechanism for implementing closures in JavaScript. For this, I will use the Chrome browser.

Let's start with the definition:
Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure “remembers” the environment in which it was created.
MDN

If something is not clear to you in this definition, it is not scary. Just read on.
')
I am deeply convinced that to understand something easier and faster with concrete examples.

Therefore, I propose to take a code snippet and walk along it along with the interpreter from beginning to end, in steps and understand what is happening.

So let's get started:


Picture 1

We are in the global context of the call, it is Global (also known as the Window in the browser) and we see that the main function already lies in the current context and is ready to work.


Figure 2

This happens because all Function Declaration (hereinafter FD) always go up in any context, they are immediately initialized and ready to go. The same thing happens with variables declared via var, only their values ​​are initialized as undefined.

It is also important to understand that JavaScript also “raises” variables declared via let and const. The only difference is that it does not initialize them as var or as FD. Therefore, when we try to access them before initialization, we get a Reference Error.

Also in main, we see the internally hidden property [[Scopes]] - this is a list of external contexts to which main has access. In our case, Global is there, since main is running in a global context.

The fact that in JavaScript initialization of references to the external environment occurs at the moment of creating the function, and not at the moment of execution, says that JS is a language with a static scope. And this is important.

Go ahead:


Figure 3

We go into the main function and the first thing that catches the eye is the Local object (in the specification, localEnv). There we see a , since this variable is declared via var and it 'floated up', well, and by tradition we see all 3 FD (foo, bar, baz). Now let's see where it all came from.

When you run any context, the abstract operation NewDeclarativeEnvironment is started , which allows you to initialize LexicalEnvironment (hereafter LE) and VariableEnvironment . Also, NewDeclarativeEnvironment takes 1 argument — an external LE, in order to create the [[Scopes]] that we talked about above. LE is an API that allows us to define the relationship between identifiers and individual variables, functions. LE consists of 2 components:

  1. Record Environment - a recording of the environment, which allows to determine the relationship between identifiers and what is available to us in the current context of the call
  2. Link to external LE. Each function has an internal property [[Scopes]] at creation

VariableEnvironment - most often it is the same as LE. The difference between them is that the value of the VariableEnvironment never changes, and the LE can change during the execution of the code. To simplify further understanding, I propose combining these components into one - LE.

Also in the current Local, there is this due to the fact that the ThisBinding call occurred - this is also an abstract method that initializes this in the current context.

Of course, each FD immediately received [[Scopes]]:


Figure 4

We see that all FD received in [[Scopes]] an array of [Closure main, Global], which is logical.

Also in the picture we see Call Stack - this is a data structure that works on the principle of LIFO - last in first out. Since JavaScript is single-threaded, only one context can be executed at a time. In our case, this is the context of the main function. Each new function call creates a new context, which is added to the stack.

At the top of the stack is always the current execution context. After the function has completed its execution and the interpreter has left it, the context of the call is removed from the stack. That's all we need to know about the call stack in this article :)

We summarize what happened in the current context:


In fact, the hard part is over. Go to the next step in the code:

Now we need to call baz to get the result.


Figure 5

A new baz call context has been added to the Call Stack. We see that a new Closure object has appeared. Here comes what is available to us from [[Scopes]]. So we got to the point. This is the closure. As you can see in Figure 4, Closure (main) goes first on the baz list of 'backup' contexts. Again, no magic.

Let's call foo:


Figure 6

It is important to know that no matter what place we call foo, it will always go after its unidentified identifiers in its [[Scopes]] chain. Namely, in main and then in Global, if not found in main.

After executing foo, it returned a value, and its context was dropped from the Call Stack.
Go to the function call bar. In the execution context, bar has a variable with the same name as the variable in LE foo - a . But, as you may have guessed, it does not affect anything. foo will still take the value from its [[Scopes]].


Figure 7

As a result, baz will return 300 and will be thrown out of the call stack. Then the same will happen with the context context, our code fragment will finish executing.

We summarize:


That's all!

I hope this article was useful to you and now you understand how the closure mechanism works in JavaScript.

Bye everyone :) And until we meet again. Like and subscribe to my channel :)

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


All Articles