The underlying operating mechanism of JavaScript closures

Source: Internet
Author: User
Tags define local

I've been studying JavaScript closures (closure) for some time now. I've just learned how to use them without having a thorough understanding of how they work. So, what exactly is a closure?

The explanations given by Wikipedia do not help much. When was the closure created and when was it destroyed? What is the specific implementation?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"Use strict";

var myclosure = (functionOuterfunction () {

var hidden =1;

return {
Inc: function innerfunction ( Span class= "line" > return hidden++;
};

myclosure.inc (); //returns 1
myclosure.inc (); //returns 2
myclosure.inc (); //return 3

// I believe that the familiar friends of JS can quickly understand this code
//so what happened behind the running of this code?
/span>

Now, I finally know the answer, I am very excited and decided to explain the answer to you. At least, I must not forget the answer.

Tell me and I forget. Teach me and I remember. Involve me and I learn.
©benjamin Franklin

And, as I read the existing data related to closures, I tried very hard to think about the connection between objects: how they were referenced, what the inheritance relationship was between objects, and so on. I couldn't find a good chart of the responsible relationships, so I decided to draw some myself.

I'll assume that the reader is already familiar with JavaScript, know what the global object is, know that the function is "first-class objects" in JavaScript, and so on.

Scope chain (Scope Chain)

When JavaScript is running, it needs some space to store local variables (locally variables). We refer to these spaces as scoped objects (scope object), sometimes called LexicalEnvironment . For example, when you call a function, the function defines some local variables that are stored in a scope object. You can think of a scoped function as a normal JavaScript object, but there's a big difference between being able to directly get this object directly in JavaScript. You can only modify the properties of this object, but you cannot get a reference to this object.

The concept of scope objects makes JavaScript very different from C and C + +. In C, C + +, local variables are stored in the stack. In JavaScript, scope objects are created in the heap (at least behave like this), so they can still be accessed without being destroyed after the function returns.

As you might think, a scope object can have a parent scope object (parented scope objects). When the code tries to access a variable, the interpreter looks for the property in the current scope object. If this property does not exist, then the interpreter will look for this property in the parent scope object. In this way, the parent scope object is always looked up until the property is found or the parent scope object is not available. We take the scope object that passes through this lookup variable in the scope chain (scope chain).

The process of finding variables in the scope chain and the prototype inheritance (Prototypal inheritance) have very similar similarities. However, the very difference is that when you do not find a property in the prototype chain (prototype chain), it does not cause an error, but it is obtained undefined . But if you try to access a property that doesn't exist in a scope chain, you'll get one ReferenceError .

The topmost element in the scope chain is the global object. In JavaScript code that runs in the global environment, the scope chain always contains only one element, which is a global object. So, when you define variables in the global environment, they are defined in the global object. When a function is called, the scope chain contains multiple scope objects.

Code running in the Global environment

Well, here's the theory. Now let's start with the actual code.

1
2
3
4
5
My_script.js
"Use strict";

1;
2;

We have created two variables in the global environment. As I said earlier, the scope object at this point is the global object.

In the above code, we have an execution context (the code of themyscript.js itself), and the scope object it references. There are many different properties in the global object, and here we ignore them.

Functions that are not nested (non-nested functions)

Next, let's look at this piece of code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"Use strict";
var foo =1;
var bar =2;

function myfunc ( //--define local-to-function variables
var a = 1;
var b = 2;
var foo = 3;
console.log ( "inside MyFunc ");
console.log ( "Outside");

//--and then, call it:
MyFunc ();
/span>

When myFunc defined, myFunc the identifier (identifier) is added to the current scope object (in this case, the global object), and the identifier refers to a function object. The function object contains the source code of the function and other properties. One of the attributes we care about is the intrinsic property [[scope]] . The [[scope]] current scope object is pointed to. That is, when the identifier of the function is created, the scope object (which is the global object) that we can directly access.

Direct access means that in the current scope chain, the scope object is at its lowest level and has no child-scoped objects.

So, console.log("outside") before being run, the relationship between the objects is as shown.

Brush it up a bit. The myFunc referenced function object itself contains not only the code of the function, but also the scope object that points to the time it was created . This is very important!

When myFunc the function is called, a new scope object is created. The new Scope object contains the myFunc local variables defined by the function, along with its arguments (arguments). The parent scope object of this new scope object is the myFunc scope object that we can access directly at run time.

So, when myFunc executed, the relationship between the objects is as shown.

Now we have a scope chain. When we try to myFunc access certain variables, JavaScript will first look for this property in the scope object (this is the one) that it has direct access to myFunc() scope . If it is not found, then it is found in its parent scope object (here is the Global Object ). If you keep looking up, and you find no parent scope object so far, you'll throw one ReferenceError .

For example, if we myFunc want to access a this variable in, then we myFunc scope can find it in the and get the value 1 .

If we try to access it foo , we'll myFunc() scope get it in 3 . JavaScript is only going myFunc() scope to look for it when it can't be found in foo it Global Object . So, here we don't have access to Global Object the inside foo .

If we try to access bar it, we myFunc() scope can't find it in it, so we find it in Global Object , so we find 2.

It is important that as long as these scope objects are still referenced, they will not be destroyed by the garbage collector (garbage collector) and we will always be able to access them. Of course, when the last reference to a scope object is dismissed, it does not mean that the garbage collector will reclaim it immediately, but that it can now be recycled .

So, when it myFunc() returns, no one is quoted myFunc() scope . When garbage collection ends, the relationship between objects becomes back to the pre-call relationship.

Next, for the sake of visualization of the chart, I will no longer draw the function object. However, always remember that the property inside the function object [[scope]] holds the scope object that is directly accessible when the function is defined.

Nested functions (Nested functions)

As stated earlier, when a function returns, no other object holds a reference to it. Therefore, it may be recycled by the garbage collector. But what if we define a nested function within the function and return it, stored by the party that called the function? (as in the following code)

1
2
3
4
5
6
7
MyFunc () {
Return Innerfunc () {
// ...
}
}

var innerfunc = MyFunc ();

As you already know, there is always a property in the function object [[scope]] that holds the scope object that is directly accessible when the function is defined. So, when we define nested functions, the nested function [[scope]] references the current scope object of the peripheral function (Outer functions).

If we return this nested function and are referenced by another identifier, the nested function and its [[scope]] referenced scope object will not be destroyed by garbage collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21st
22
23
24
"Use strict";

functionCreatecounter (Initial) {
var counter = initial;

functionIncrementValue) {
Counter + = value;
}

function get ( return counter;
return {

var myCounter = Createcounter (100);
console.log (Mycounter.get ()); //returns the
mycounter.increment (5);
console.log (Mycounter.get ()); //back to

When we call that createCounter(100) moment, the relationship between the objects such as

Note increment and get the function both have createCounter(100) scope a reference to the pointer. If createCounter(100) there are no return values, createCounter(100) scope they are no longer referenced and can then be garbage collected. But since createCounter(100) there is actually a return value, and the return value is stored in myCounter , the reference relationship between the objects becomes as shown

So, createCounter(100) although it has been returned, its scope object still exists and can only be accessed by nested functions ( increment and get ).

Let's try to run myCounter.get() . As I said, a new scope object is created when the function is called, and the parent scope object of the scope object is a scope object that is currently accessible directly. So, when myCounter.get() it is called, the relationship between the objects is as follows.

In the myCounter.get() process of running, the object at the bottom of the scope chain is get() scope , this is an empty object. Therefore, when myCounter.get() the counter variable is accessed, JavaScript cannot get() scope find the attribute in it, so it is createCounter(100) scope searched upward. Then, myCounter.get() return this value.

myCounter.increment(5)when called, things get more interesting because the arguments are passed in when the function is called.

As you can see, increment(5) the call creates a new scope object that contains the arguments passed in value . When the function tries to access value it, JavaScript can immediately find it in the current scope object. However, when this function tries to access counter it, JavaScript cannot find it in the current scope object, so it looks in its parent scope createCounter(100) scope .

We can note that outside of the createCounter function, get there is increment no other place to access this variable except for the returned and two methods value . This is the way to implement "private variables" with closures .

We notice that the initial variable is also stored in the createCounter() scope object that was created, although it is not used. So, we can actually get rid var counter = initial; of it and will initial rename it counter . But for the sake of readability of the code, we keep the original code from changing.

It is important to note that the scope chain is not replicated. Each function call only adds a scope object below the scope chain. Therefore, if the variables of any one scope object in a scope chain are modified during a function call, then the function object that also owns the scope object in the scope chain can access the variable.

This is why the following example, which everyone is familiar with, will not produce the results we want.

1
2
3
4
5
6
7
8
9
"Use strict";

Document.getelementsbyclassname ("MyClass"), I;

0; i < elems.length; i++) {
Elems[i].addeventlistener (function () {
this.innerhtml = i;
});
}

Multiple function objects are created in the preceding loop, and all function objects [[scope]] hold references to the current scope object. The variable is i exactly in the current scope chain, so each modification of the loop i is visible to each function object.

"Looks the same" function, not the same as the scope object

Now let's look at a more interesting example.

 1 
2
3
Span class= ' line ' >4
5
6
7
8
   function  Createcounter (initial) {
//...
var myCounter1 = Createcounter (100);
var myCounter2 = Createcounter (200);

When myCounter1 and myCounter2 is created, the relationship between the objects is

In the above example, myCounter1.increment myCounter2.increment the function object has the same code as the same property values ( name , and length so on), but they point to a different [[scope]] scope object .

That's the result.

1
2
3
4
5
6
7
8
9
10
11
var a, B;
A = Mycounter1.get (); //A equals 100
b = Mycounter2.get (); //b equals 200

Mycounter1.increment (1);
Mycounter1.increment (2);

Mycounter2.increment (5);

A equals 103.
b equals 205.
Scope Chains and this

thisValue is not saved in the scope chain, this the value depends on the scenario when the function is called.

Translator Note: For this part, the translator has written a more detailed article, please refer to the "Natural language perspective of JavaScript in the" this keyword. This part of the original text and the " this use in nested functions" translator will no longer translate.

Summarize

Let's think back and forth about some of the issues we mentioned at the beginning of this article.

    • What is a closure package? Closures are the most wanted to include both the function object and the scope object reference. In fact, all JavaScript objects are closures.
    • When was the closure created? Because all JavaScript objects are closures, when you define a function, you define a closure.
    • When was the closure destroyed? When it is not referenced by any other object.
Proper noun translation table

This article uses the following Terminology translation table, if there is a better translation please inform, especially * the addition of translation

    • * Code running in the global environment: top-level
    • Parameter: Arguments
    • Scope Objects: Scope object
    • Scope Chain: Scope Chain
    • Stack: Stack
    • Prototype Inheritance: Prototypal inheritance
    • Prototype chain: Prototype chain
    • Global object: Globally object
    • Identifier: identifier
    • Garbage collector: Garbage collector

The underlying operating mechanism of JavaScript closures

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.