One of the reasons why so many people in the world are mediocre is the fear of doing something wrong, of taking responsibility. They are unable to form independent ideas and dare not express their opinions. – scrolls

Many of the examples and questions you’ve seen in the past have evolved from the interview questions themselves. Even so, there are still quite a few “variations” worth mentioning in the interview questions involving closures. In this section, we will “brush the question” around closures and break down the main ideas of closures in the interview process one by one.

“Loop body and closure” series

The combination of closure and loop body is the most classical way of propositional closure. With a simple question, an interviewer can get a lot out of what he wants to hear.

Let’s look at a topic that you’re probably very, very familiar with:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
console.log(i);
Copy the code

Q: What is the output of this code? (Run the code in your head and remember your answers)

If it’s a little vague, you might say something like:

0, 1, 2, 3, 4, 5Copy the code

Let me guess what you’re thinking: the for loop outputs I values from 0 to 4 one by one. SetTimeout is something I’ve seen before, but it doesn’t seem to matter here.

So, you give the above answer ~

If you are familiar with the setTimeout function, it is not difficult to give an “evolved” answer:

5, 0, 1, 2, 3, 4Copy the code

The for loop prints I values from 0 to 4, but setTimeout delays the output, so the last line is executed first. This last line outputs the final state of I (5), and then after 1000ms, 0 to 4 will be printed one by one.

However, if you are familiar with setTimeout functions and have had a fair amount of interview experience (or have fully understood our previous sections on closures and scopes), you can give the correct answer:

5, 5, 5, 5Copy the code

Why is that answer?

The last line of console, as you can see, is 5; The last line is printed first, which is understandable (setTimeout is delayed). The key is what’s happening in the for loop, so we’re going to stroke this:

The setTimeout in the for loop is executed five times, delaying the execution of the function by 1000ms each time:

function() {
  console.log(i);
}
Copy the code

If you look at this function, does it have no I variable in its scope at all?

So given what we’ve learned about scopes, in order to print I, do we have to go to the upper scope?

If you think about it, the first time this function is executed, 1000ms later, it’s trying to go out into the upper scope (in this case, the global scope) to find a variable called I. At this point, the for loop has already been executed, and I has entered the final state — 5. So 1000ms later, when this function is actually executed for the first time, the value of I referenced is already 5. The overall scope state when the function executes is shown as follows:

The corresponding scope chain relation is as follows:

The same setTimeout callback will then be executed four times, every 1000ms, trying to print the same global I, so each time it will print 5.

There are three ways to transform it

It loops five times and outputs a value each time, which is clearly bug-level output. If we want I to be printed from 0 to 4, how can we rewrite this so that we can do that?

The first idea is:

We can use the third argument to the setTimeout function. Remember that setTimeout can take an infinite number of arguments from the third entry position. These arguments exist as additional arguments to the callback function.

Here we can store the value of I in each loop in the third parameter of setTimout:

for (var i = 0; i < 5; i++) {
    setTimeout(function(j) {
        console.log(j);
    }, 1000, i);
}
Copy the code

Second idea:

Set a layer of functions outside of setTimeout and use the input parameter of the external function to cache the value of I in each loop:

var output = function (i) { setTimeout(function() { console.log(i); }, 1000); }; for (var i = 0; i < 5; I ++) {// here I is assigned to the output variable I output(I); }Copy the code

A third way of thinking:

It is similar to the second idea, which is to set a layer of function around setTimeout, but this function is an immediate function. Caches the I value in each loop with the input parameter of the immediate-execute function:

for (var i = 0; i < 5; I ++) {// here I is assigned to the variable j (function(j) {setTimeout(function() {console.log(j); }, 1000); })(i); }Copy the code

Know a solution hundred — say the real question, all “variation” :

Above this topic, also ask you to cause attention, this is a motif in the motif. If you really understand what this is all about, you’ll see that it’s all about closures and loops together. Below, we use two questions to test you:

What is the output of the following two questions? Why?

function test (){
    var num = []
    var i
    for (i = 0; i < 10; i++) {
        num[i] = function () {
            console.log(i)
        }
    }
    return num[9]
}
test()()
Copy the code
var test = (function() {
    var num = 0
    return () => {
        return num++
    }
}())
for (var i = 0; i < 10; i++) {
    test()
}
console.log(test())
Copy the code

“Complex scope” series

The most efficient way to look at closures is to look at the code and explain the results, and then ask the candidate how he or she came to the conclusion. There are two main directions to this propositional approach, one is the loop closure problem we just covered, and the other is the “complex scope”.

Complex scoped questions are frequent but not complex. Its formula is very simple and crude – in a problem, try to give you as many scopes as possible (sometimes also mixed with some more fragmentary JS grammar knowledge), in order to leave you completely confused.

How do I not get blindsided? Here’s just a tip for you to do:

Memories we learn before closure process, is the basic of each code, I will give you in the form of a “square set square” this graphic scope?

Draw pictures – This is an important way to avoid being confused.

When doing this, it’s best to ask the interviewer for a piece of paper and a pen (or just use your imagination, that’s fine). Start with the function being executed and draw the global map of the scope layer by layer from the inside out, then look at the picture and speak for yourself!

A lot of you might not be impressed by what I’m saying, but I suggest you try it, just once, and you’ll see that the complexity of the problem can be very, very low. As long as you don’t have a problem with the knowledge itself (go back and review the previous two sections a few times if you have a problem), it will be very difficult for you to get this kind of problem wrong after drawing a picture and positioning the variables according to the picture.

Let’s practice this method through a real problem:

Sample questions:

Step1: read the topic

var a = 1;
function test(){
    a = 2;
    return function(){
        console.log(a);
    }
    var a = 3;
}
test()();
Copy the code

Step2: Draw a picture (to do the problem, everyone should begin to draw with me ~)

  • Layering: We draw from the inside out. The innermost layer is an anonymous function. The outer layer of the anonymous function is the scope of the test function, and the outer layer is the global scope.

  • Find variable: this is the difficulty of this problem! There is no doubt that a =1 in the global variable and no doubt that the scope of a in the innermost anonymous function does not exist; The key is what happens to this a in test?

Test (a); test (a); test (a);

  1. Var a = 3; var a = 3; var a = 3; But! Remember, there is also a variable promotion inside the scope of the function, and the var will be raised at the top of the function, so the test body is equivalent to the following:
Function test(){var a = 2; return function(){ console.log(a); } a = 3; }Copy the code
  1. Scope rule: Considering the hierarchy we divided in Step1 and the variables in the test function analyzed in the previous step, it is not difficult to see that the anonymous function actually gets a in the test scope. So is this a 2 or 3? I want you to remember what we said earlier in the lecture:

Our scope division depends on where you put it as you write it. The scope delineated like this follows the lexical scope model.

On the blackboard! Be sensitive to the timing of writing! Here our anonymous function is defined before the assignment of a = 3 has occurred (only the declaration will be advanced!). So it gets an A of 2!

We fill in the corresponding fields one by one with the variables we have analyzed:

The scope chain can be clearly traced as follows:

And you’re done! The answer is 2

Real (Practice) :

What does the following code output? Why is that?

function foo(a,b){
  console.log(b);
  return {
    foo:function(c){
      return foo(c,a);
    }
  }
}
 
var func1=foo(0);
func1.foo(1);
func1.foo(2);
func1.foo(3);
var func2=foo(0).foo(1).foo(2).foo(3);
var func3=foo(0).foo(1);
func3.foo(2);
func3.foo(3);
Copy the code