Revisit JavaScript(lesson4): scope and closures

Posted by kurtis on Fri, 18 Feb 2022 03:19:17 +0100

In lesson3, we reviewed the content related to the JS scope, understood the JS scope, and looked at the closure again, which was very easy.

1. Concept of closure

Let's add a knowledge point first (PS: if you don't think it's easy to understand, just look at the code ~), Lexical scope: "The execution of a function depends on the variable scope, which is determined when the function is defined, not when the function is called. In order to realize the lexical scope, the internal state of JS function object not only contains the code logic of the function, but also must refer to the current scope chain. Function objects can be related to each other through the scope chain, and the variables inside the function body can be changed This feature is called closure (JavaScript authority Guide)

2. The function of closure

Closures allow functions to access and manipulate variables outside functions. Closures enable functions to access variables or functions as long as they exist within the scope in which they were declared. Look at a very simple code:

var  outerValue = 'New_Name';
function outerFunc() {
  console.log(outerValue);
  //New_Name
}
outerFunc();

We declare the variable outerValue and the function outerFunc in the same scope (global scope in this case), and then execute outerFunc. It can "see" and access outerValue. You may have written many similar code snippets, but you may never realize that you are creating a closure.

Let's take another familiar example:

var outerValue = 'New_Name';
var laterFun;
function outerFun() {
  var innerValue = 'Revisit new knowledge';
  function innerFun(){
    console.log(outerValue);
    console.log(innerValue);
  }
  laterFun = innerFun;
}
outerFun();
laterFun();
//New_Name
//Revisit new knowledge

Statements in the above code: laterFun = innerFun; The function of is to store the reference of the internal function innerFun on the variable laterFun. Because laterFun is within the global scope, it can be called. Statement: laterFun(); The function of is to call internal functions. We cannot call innerFun directly in the global scope, because it is within the function scope of outerFun.

So we see that closures allow us to execute internal functions after the scope of internal functions disappears. This is because when you declare an internal function in an external function, you not only define the declaration of the internal function, but also create a closure. This closure contains not only the declaration of the inner function, but also all variables in the scope when the inner function is declared. When the internal function is finally executed, although the declared scope is gone, the original scope can still be accessed through closures. Is this much like an internal function "wrapping" variables? So it is called closure.

Let's take another example of closure in other forms:

let outerFun;//Undefined global function
{
  let blockValue = 'New_Name';//Block scope variable
  outerFun = function () {
    console.log(blockValue)//New_Name
  }
}
outerFun();

outerFun is assigned within a block, and the block (and its parent scope, i.e. global scope) constitute a closure. Wherever outerFun is called, it has access to variables within the closure. We should note that although the program has exited the scope of blockValue when calling outerFun, it still has permission to access it. Generally, after a scope exits, the variables in the scope will die out. For the closure in this example, JS will detect that the function outerFun is defined within the specified block scope, and this function outerFun can be referenced outside the block scope, so outerFun is allowed to hold the access permission of the block scope. That is, the function outerFun in the closure affects the declaration cycle of the closure.

3. Closure and circulation

many people talk about closures together with loops. Let's have a look~

for(var i=1; i<=5;i++) {
  console.log(i);
}
//1 2 3 4 5

This code has no problem at all. It outputs 1-5 at a time. But adding some ingredients is completely different:

for(var i=1; i<=5;i++) {
  setTimeout(()=>{
    console.log(i);
  },i*100) 
}
//6 6 6 6 6

We wanted to see this in the console: print 1 ~ 5 at 100ms intervals. But in fact, this is the case: 6, 6, 6, 6 and 6 are printed at an interval of 100ms. Why? Where did 6 come from? The planting condition of circulation is that i does not meet less than or equal to 5. The value of i at the end of the first cycle is 6.

In fact, take a closer look, this is also in line with expectations. The callback function of setTimeout is executed after the end of the loop, so a 6 is output every time. What is the reason why the behavior of this code is different from that implied by semantics?

The reason is that we want to get a i big copy in each iteration of the loop. However, according to the working principle of scope, although the five functions in the loop are defined separately in each iteration, they are all in a shared global scope (or they are enclosed in a scope), so there is actually only one i. How to solve it? Is to introduce more closure scopes. (excerpt from JavaScript you don't know (Volume i)) IIFE creates a scope by declaring and immediately executing a function. Take a look at the following code first:

 for(var i=1;i<=5;i++) {
  (function() {
    setTimeout(()=>{
      console.log(i);
    },i*100)
  })();
}
// 6 6 6 6 6 

The result of running this code is still a 6 output every 100ms, and the result is not as expected. This is because although each setTimeout function will say that the scope created by IIFE in each iteration is closed. However, the scope is empty and has no substance. The referenced i is still the public i. So let's add something to it:

for(var i=1;i<=5;i++) {
  (function() {
    var j = i;
    setTimeout(()=>{
      console.log(j);
    },j*100)
  })();
}
// 1 2 3 4 5

In IIFE, we define the local variable j to store the value of i, so that each setTimeout function has its own j, and close it to maintain access to the variable j. The following code improves it by using parameters:

for(var i=1;i<=5;i++) {
  (function(j) {
    setTimeout(()=>{
      console.log(j);
    },j*100)
  })(i);
}
// 1 2 3 4 5

The principle is the same as above. Using IIFE in an iteration will generate a new scope for each iteration, so that the callback of the delay function can close the new scope inside each iteration, and each iteration will contain a variable with the correct value for us to access.

After all, what are the use scenarios of closures in actual development?

4. Usage scenarios of closures

Let's talk about two common use scenarios of closures: (1) encapsulating private variables (2) callback functions. Let's take a look at the example of encapsulating private variables:

function People() {
  var age = 0;
  this.getAge = function() {
    return age;
  }
  this.grow = function() {
    age ++;
  }
}
var someone = new People();
someone.grow();
console.log(someone.age)
//undefined , indicates that we cannot obtain the variable value directly
console.log(someone.getAge());
//1. The getAge method can be used to obtain
var anotherone = new People();
console.log(anotherone.getAge());
//0 # another one has its own age attribute

We created a People constructor and defined the age inside the constructor. Due to the limitation of JS scope rules, we can only access this variable inside the constructor. In order for code outside the scope to access age, we define the getAge method to access the variable.

Callback function is another common scenario of using closures. In callback function, we may access external data frequently, so we can create closures with access to external data. The callback function is similar to the essential principle of setTimeout described above, so I won't introduce it too much here.

The above is what we need to know about closures. Can you understand them? Anyway, I will ~ ~, let's do some tests together

5. Test questions

Guess the result of the following code:

var outerValue = 'outer';
var laterFun;
function outerFun() {
  var innerValue='inner';
  function innerFun(param){
    console.log(outerValue);
    console.log(innerValue);
    console.log(param);
    console.log(amazing);
  }
  laterFun = innerFun;
}
var amazing = 'New_Name';
outerFun();
laterFun("so fun");

The result is: the console outputs outer, inner, so fun, new in turn_ Name . It is worth mentioning here that (1) we added a parameter to innerFun, which is also in the closure. (2) In addition, although the variable amazing appears after the declaration of the function, it is also contained in the closure. In short, all variables in the peripheral scope are contained in closures.

OK, that's all for our review of JS today. We'll review the knowledge of functions next time~

Please correct any mistakes. Review the old and know the new. Welcome to review the old knowledge and climb a new level with me~

reference material:

[1] JavaScript you don't know (Volume I)

[2] JavaScript Ninja script Second Edition

[3] JavaScript authority Guide 6th Edition

[4] JavaScript programming essence

[5] JavaScript learning guide 3rd Edition

Topics: Javascript ECMAScript