As the book JavaScript You Don’t Know puts it:

Closures are like a mysterious, uncivilized world separated from JavaScript, and only the bravest can get there!

This is true in actual development work, except in the interview scenario, or in a few other specific scenarios (such as the “shockproof throttling” function), where I realize that this is the “closure”! Other times, they don’t use it, or they don’t know it.

What a pity!! It’s such a good thing and you don’t even know it.

I Never understood JavaScript closures in medium I Never understood JavaScript closures in Medium (If you don’t know how to find me, find Jesus is useless, I said QAQ)

After reading this article, you may be surprised to find that even the following code has closures. !

let a = 1
function b(){
    return a
}
console.log(b())
Copy the code

“I will never understand JS closures because they are everywhere……”

Execution context

Before we learn about closures, it’s important to understand the concept of “execution context”!

The execution context is divided into:

  • Global Execution Context: When a JS file is loaded into the browser to run, it is entered into the Global execution context. Global variables are in this execution context. The code is accessible from anywhere.
  • Functional Execution Context: A context defined within a specific method. Access is only available within the method and internal methods within the method.

For example, when we call a function in the context of global execution, the process of javascript parsing would look something like this:

  1. JS creates a new function execution context (understood as a temporary “execution context”) with a locally accessible set of variables;
  2. The execution context will be executed in the execution stack (which is used here as a mechanism to trace the execution location of the program);
  3. When faced withreturn}, the execution is concluded.
  4. The execution context pops on the execution stack;
  5. The executed function sends its return value to the calling execution context, in this case to the global execution context;
  6. The function execution context is destroyed and its set of variables is no longer accessible, which is why it is called a temporary “execution context”;

To perform the stack operation, see the following GIF understanding:

Let’s take a look at what JS does step by step:

1: let a = 3
2: function addTwo(x) {
3:   let ret = x + 2
4:   return ret
5: }
6: let b = addTwo(a)
7: console.log(b)
Copy the code
  1. The first line declares a variable a in the global execution context and assigns the value 3.

  2. Lines 2 through 5 contain the function execution context. The addTwo function is declared in the global execution context. The code inside the function is not executed, but stored for later invocation.

  3. In line 6, we declare a variable b and assign it to the return value of addTwo.

  4. Find the addTwo function in the global execution context and execute it, passing in the argument 3;

  5. At this point, the execution context switches! Create a temporary function execution context named addTwo and push it onto the execution stack.

  6. Go to the third row, and then what? Create a variable ret? No, actually, you create a variable x and you assign it to 3;

  7. In the third line, declare a variable ret and assign it to the result of the x + 2 operation.

  8. JS is going to find the addition term where’s x? X is already in the context of the addTwo function, it has a value of 3, is added to 2, and is assigned to ret.

  9. Then we go to the fourth line and return ret;

  10. In lines 4 and 5, the addTwo function is executed and the temporary execution context is destroyed. Both x and ret will be cleared; The function execution context is pushed off the call stack, and the function is returned to the calling context, in this case the global execution context;

  11. Then go back to point 4 and assign the return value of the function to b;

  12. Line seven, print;

This is a very lengthy explanation, and it doesn’t cover “closures” yet, but it’s something we need to get up front and understand.

Lexical scope

Next to execution context, there’s another concept we need to understand: lexical scope!

Look at the following code example:

1: let val1 = 2
2: function multiplyThis(n) {
3:   let ret = n * val1
4:   return ret
5: }
6: let multiplied = multiplyThis(6)
7: console.log('example of scope:', multiplied)
Copy the code

Can you describe this code as explained in the previous section?

  1. The first line declares a variable vall in the global execution context and assigns it a value of 2.

  2. Lines 2 through 5 declare a context for execution of the multiplyThis function. The internal code is stored for calling without execution.

  3. Const multiplyThis; const multiplyThis; const multiplyThis; const multiplyThis;

  4. MultiplyThis function is found and executed in the global execution context, passing in parameter 6;

  5. At this point, the execution context switches! Create a temporary function execution context named multiplyThis and push it onto the execution stack.

  6. On the third line, declare a variable n in the context of the function execution and assign the value 6;

  7. In the third line, declare a variable ret and assign it to n as the result of multiplication with vall. N is 6. What about vall? No vall found in current function execution context!

  8. In this case, JS will call the global execution context of multiplyThis function to find vall! Got it! Its value is 2, 6 times 2 is 12. Assigned to ret;

  9. Then we go to the fourth line and return ret;

  10. Fourth, the fifth line, multiplyThis function performs over, temporary execution context is destroyed, the variables n and ret will be cleared, but the vall is not destroyed, because it exists in the global function execution context;

  11. Return to the sixth row and assign the return value 12 to the variable multiplied;

  12. Finally print out;

In this description, the steps to ash are basically the same as the description in the previous section

Most importantly: when JS looks for a variable in the current execution context, if it cannot be found, it will call the previous execution context to look for it.

This is not difficult to understand, such a chain lookup variable process, is JS [scope chain].

Function return function

A function can return anything, including another function.

Let’s parse the following code again as above:

 1: let val = 7
 2: function createAdder() {
 3:   function addNumbers(a, b) {
 4:     let ret = a + b
 5:     return ret
 6:   }
 7:   return addNumbers
 8: }
 9: let adder = createAdder()
10: let sum = adder(val, 8)
11: console.log('example of function returning a function: ', sum)
Copy the code
  1. The first line declares a variable in the global execution context, val, and assigns the value 7.

  2. Lines 2 through 8 declare a createAdder function execution context. The internal code does not execute and is stored for call.

  3. In line 11, declare a variable adder and assign it to the value returned by createAdder.

  4. Find the createAdder function in the global execution context and execute it. It is called in the second line, OK;

  5. In the second line, create a new temporary function execution context and push it off the execution stack;

  6. Since createAdder does not pass any parameters, it goes directly into the function, so we declare a function addNumbers, which declares, but does not execute;

  7. Go to line 7 and return addNumbers to the global execution context, which is a function; The context of the temporary createAdder function is then pushed off the execution stack;

  8. The createAdder execution context is destroyed, and addNumbers will not exist;

  9. On line 10, we declare a variable in the global execution context, sum, whose value is the return value of adder(val, 8);

  10. Let’s go to the global execution context and look for adder, and there it is, and it happens to be a function, so we can call it;

  11. It takes two parameters, the first is val, which is declared and copied in the first line, as 7, and the second parameter is 8;

  12. Then we go to lines 3 through 5 and create two variables, a and b, and assign them values 8 and 7;

  13. In the fourth line, declare a variable ret and assign the value 8 + 7, which is 15;

  14. The ret variable is returned, the temporary function execution context is destroyed, a, b, and ret variables are destroyed;

  15. The return value from the adder function is assigned to the sum variable;

  16. Finally print out;

Protagonist closures!!

After looking at the detailed analysis of the above three specific steps, I believe that I will give you a similar call code, you must be able to understand the clue, make a similar analysis!

1: function createCounter() {3: function counter = 0 4: const myFunction = function() {5: counter = counter + 1 6: return counter 7: } 8: return myFunction 9: } 10: const increment = createCounter() 11: const c1 = increment() 12: const c2 = increment() 13: const c3 = increment() 14: console.log('example increment', c1, c2, c3)Copy the code

Why don’t you try it?

  1. The first line declares a variable counter and assigns undefined;

  2. Lines 2 through 9 declare the function createCounter in the global execution context, do not execute it, and store it for call.

  3. In line 10, increments is declared in the global context, and is assigned to the value returned by the createCounter function;

  4. Call the createCounter function, which was declared in step 2, and execute it!

  5. Lines 2 through 9 create a new temporary function execution context.

  6. Declare a variable counter in the context of the temporary function execution and assign it to 0;

  7. Lines 4 through 7 declare a myFunction function that is not executed and stored for call.

  8. Return myFunction to increment. If the createCounter function execution context is destroyed, both myFunction and counter will be destroyed.

  9. Now the global execution context doesn’t have myFunction anymore, it has increment;

  10. In line 11, declare a variable c1 and assign it to the return value of increment;

  11. Increment is the return value of the createCounter function, and is defined in lines 4 through 7;

  12. Create a new function execution context. Enter the execution context of the function directly.

  13. Line 5, counter = counter + 1, is the counter variable found in the newly created function execution context? Can’t find! You have to go back to the global execution context where it was called, where counter is undefined, so counter = undefined + 1;

  14. Line 6, return counter with the value 1, destroy the new function and execute the previous script;

  15. Going back to line 11, c1 is assigned 1;

  16. In line 12, repeat steps 10 through 14. Similarly, c2 is assigned 1;

  17. In line 13, repeat steps 10 through 14. Similarly, set C3 to 1;

  18. Final print output;

Is that true? We print in the console:

The result is actually contrary to the expected print of our analysis!

What happened?

There must be another mystery at work! Yes, it’s a “closure”! The missing piece!

Here’s how it works:

When we declare a function, we store it for call, storing not only the definition of the function, but also the function’s “closure,” which includes the lexical scope of all variables in the execution context of the function. These are determined when the function is created.

To use an unfortunate metaphor, think of a function as a person who, when born (when the function is created), also comes with a backpack (closure) that includes the home environment (lexical scope).

So our step-by-step analysis in the last paragraph was wrong!

Let’s do the right analysis again!

1: function createCounter() {3: function counter = 0 4: const myFunction = function() {5: counter = counter + 1 6: return counter 7: } 8: return myFunction 9: } 10: const increment = createCounter() 11: const c1 = increment() 12: const c2 = increment() 13: const c3 = increment() 14: console.log('example increment', c1, c2, c3)Copy the code
  1. The first line declares a variable counter and assigns undefined; (ditto)

  2. Lines 2 through 9 declare the function createCounter in the global execution context, do not execute it, and store it for call. (ditto)

  3. In line 10, increments is declared in the global context, and is assigned to the value returned by the createCounter function; (ditto)

  4. Call the createCounter function, which was declared in step 2, and execute it! (ditto)

  5. Lines 2 through 9 create a new temporary function execution context. (ditto)

  6. Declare a variable counter in the context of the temporary function execution and assign it to 0; (ditto)

  7. Lines 4 through 7 declare a myFunction function and create a closure that includes the lexical scope of all variables in the function’s execution context. In this case, it’s counter, looking through the scope chain, which has a value of 0. Again, there’s no execution, it’s stored for call;

  8. Return myFunction with its closure and assign it to the variable Increment. If the createCounter function execution context is destroyed, both myFunction and counter will be destroyed.

  9. Now the global execution context doesn’t have myFunction anymore, it has increment; (ditto)

  10. In line 11, declare a variable c1 and assign it to the return value of increment; (ditto)

  11. Increment is the return value of the createCounter function, and is defined in lines 4 through 7; (ditto)

  12. Create a new function execution context. Enter the execution context of the function directly. (ditto)

  13. Line 5, counter = counter + 1, is the counter variable found in the newly created function execution context? Can’t find! We’re going to look in its “closure”! , there is! It has a value of 0, so we can say counter = 0 + 1, which is equal to 1;

  14. Line 6, return counter with the value 1, destroy the new function and execute the previous script; (ditto)

  15. Going back to line 11, c1 is assigned 1; (ditto)

  16. In line 12, repeat steps 10 through 14. When we go to the “closure” again, because counter has changed to 1 after the previous operation, we perform the addition operation again, and the result of counter is 2.

  17. In line 13, repeat steps 10 through 14, and similarly assign 3 to C3;

  18. Final print output;

Oh, I see!

When a function is declared, not only is it declared, but it also comes with a “closure”. The closure contains the lexical scope of all variables in the execution context of the function!

So this is a closure!

You may wonder: if a declared function contains a closure, does a global function also have a closure?

The answer: YES!

When declaring global functions, there are closures too! It’s just that its variable lexical scope is global, so it’s not obvious that it’s not closed.

Closures are most obvious when a function returns another function! The variable lexical scope of the returned function can access variables that are not globally scoped; they are only placed in their closure!

test

Here are a few more closure questions for your self-examination:

Function addX(x) {return function(n) {return n + x}} const addThree = addX(3) Console. log('example partial application', d) function multiply(number1, number2) {if (number2! == undefined) { return number1 * number2; } return function doMultiply(number2) { return number1 * number2; }; } multiply(4, 5); const double = multiply(2); double(5); Function showBiBao() {for (var I = 0; i < 5; i++) { setTimeout( timer => () { console.log(i); }, 1000); } console.log(i) } showBiBao() function showListNumber() { for(var i = 0; i < 5; i++) { let ret = function(i) { setTimeout(function timerr() { console.log(i) }, 1000) } ret(i) } console.log(i) } showListNumber()Copy the code

I’m sure if you follow the analysis steps carefully from beginning to end, it’s not a big deal

Welcome to discuss ~

conclusion

As we know from this section: wherever there is a function declaration, there is a closure! But sometimes the “closed” is not so obvious.

Why do they say “I will never understand JS closures”? As you can see, even if you don’t use closures or don’t know about closures, you can’t clock in and out of work. But are closures the key?

No!

You think this is about closures? Wrong! This article is about execution context!

You think this article is about execution context? Wrong! This is about lexical scope!

You think this is about lexical scope? Wrong! This article is talking about the dynamic language features of JS!

Are you talking about the dynamic language of JS? Wrong! This article is talking about the single threaded feature of JS running!

.

Yeah, well, it’s just fun

✍, writing is not easy to encourage 👍, your feedback 💬, my motivation 🚀

I am nuggets Anthony, pay attention to the public number [Nuggets Anthony], pay attention to front-end technology, also pay attention to life, continue to output ING!