1. What are closures?

Interviewer asks: Watt is closure?

A: I node, closure is blah blah blah blah…

MDN: A closure is formed by the function and its references to its state, the lexical environment. That is, closures allow you to access outer function scopes from inner functions. In JavaScript, a function generates a closure each time it is created.

Intuitive expression:

function father() {
    var name = "Dede"; // name is a local variable created by father
    function child() { // Child () is an inner function, a closure
        console.log('My father name is ' + name); // Use a variable declared in the parent function
    }
    child();
}
father();
/ / output
// My father name is Dede
Copy the code

2. What do closures do?

Blah blah blah…. I believe everyone is familiar with it

Main uses:

  • 1. Make it possible for external access to internal variables of functions;

  • 2. Local variables are resident in memory; (High-order functions such as throttling and anti-shaking) (Advantages and disadvantages)

  • 3. Avoid using global variables to prevent pollution of global variables;

3. Pros and cons of closures?

Blah blah blah….. I’m sure you can say the same

  • Can cause memory leaks (when a chunk of memory is occupied for a long time and not released)

Analysis of 4.

Many people will be able to answer these basic concepts more or less in an interview, but how many of them just memorize the answers without really understanding the concepts?

Let alone how to use it flexibly.

  • Many higher-order functions are involved in the use of closures, such as throttling, anti-vibration, promise and other higher-order functions;
  • Closures are also used in other plug-ins and frameworks.

Conclusion 5.

Closures can be said to be the first watershed in js language learning career. —- comes from “big” big small foolish

Engineers who really understand closures are starting to break through.

How many concepts are involved in closures?

  • 1. Application of the most basic functions

  • 2. Scope of variables

  • 3. Memory allocation, pointer to

  • 4. Garbage collection mechanism

The scope can be extended to refer to this and so on…

After the allocation of memory, the garbage collection mechanism follows…

Wait, you think it’s really over? It’s just begun!

6. A classic interview question

6.1 the first question

Can you figure out exactly what happened at each step and what the end result was?

Memory allocation in JS, and variable reference (pointer) problem.

I like to use Pointers to describe the concept of this reference, which may not be quite right, but it’s easy to understand.

6.2 the second question

Ok, this one is a little difficult, let’s start with a small demo:

This problem, a little JS reference type of children’s shoes, should all know:

  • 1. A is not actually {name: lili}, just a reference to the object’s address.
  • {name: lili} = {name: lili} = {name: lili} = {name: lili} = {name: lili}
  • 3. Reassign the name attribute of the object pointed to by a to cat. Now the object is {name: cat}.
  • 4. Print a.name, first find the memory space pointed by A, then fetch the data, and finally print cat.
  • 5. Print b.name, because b also refers to this object, so it prints cat.

You can test that a===b a is exactly equal to B, because they’re both reference types, just Pointers, referencing the same address.

This is not going to be a difficult problem but it’s going to be easy to understand a little bit about reference types.

6.3 Solve problem 1

Return to the topic

Finding this one is a little more complicated, and we’re going to do it step by step:

  • 1. First, JS will allocate a space in memory for storing {n: 1} data (here called Ming Ming), and then assign the address reference of this space to A.
  • 2. Variable b also refers to the object {n: 1} clearly.
  • 3. A.x =a={n: 2}, this step seems to be controversial, I don’t know.
  • 4. I think a more convincing solution is that whether a.x=a={n: 2} or a=a.x={n: 2}, the assignment order has precedence, just like the concept of the operator multiplication and division having precedence over addition and subtraction. Here, the object property access operation takes precedence over the object access operation.
  • X ={n: 2} ={n: 2} ={n: 2} ={n: 2} ={n: 2}
  • 6. If the assignment continues, variable A will be redirected to a new address that holds {n: 2}.
  • 7. Here is clear, and finally a reference is a new address, the address for the second dog child objects is {n, 2}, then access to the object’s properties by a.x x, but the object has only one attribute is n, then will print out the undefined.
a = {
    n: 2
}
a.x  === undefined
Copy the code
  • 8. The data structure in the object is:
b = {
    n: 1.x: {
        n: 2}}Copy the code
  • 9. And try b.x === a, which will print true.

Personally, I think this is the best way to understand the concept, and I am also learning, if there is a bigwigs point out the wrong place, I will humbly accept.

6.4 Let’s test the idea again

  • 1. First allocate a space for object {name: bibi}, and then assign the address reference of this object to obj1.
  • 2. Assign an object to obj2 using getObj.
  • 3. We pass obj1 as an argument, so we pass a pointer, and inside the function we change the name property of the object to jock.
  • 4. Reallocate a space to the object {name: lili}, assign the object’s address reference to the parameter obj, and return the pointer obj.
  • 5. The variable obj2 will get a reference to the address of the object {name: lili}, which is the pointer to the new object.

Finally, print obj1, which will print name=jcok. Print obj2, which will print name=lili. And obj1 === obj2 will yield false.

Ok, this part is the memory allocation, pointer address reference part, no further.

6.4 Garbage collection mechanism

Now that you’ve read this, you should be a little clearer about the concept of reference types, and it will be much easier once you have Pointers in mind.

When it comes to memory allocation, there is a natural release of memory. The garbage collection mechanism is a process of memory release. JS automatically reclaims those allocated Spaces, and it has some corresponding policies.

In the simplest terms, a memory space is allocated. If there is no address reference after the space is used, that is, no pointer from the root points to the address space, then JS will send troops to clear the data of the address space, and then release the memory occupied by this space.

This indicates that none of the values accessed from the root node should be deleted. It is possible that an object that has one or more Pointers to it, but no references or Pointers from the root, will also be reclaimed. The technical term is “reachable,” which means accessible.

JS garbage collection mechanism can be generally divided into two blocks: tag removal and reference counting.

If a = null, it does not delete the object pointed to by a

A lot of times, it’s assumed that if I assign a null value to a, it means THAT I deleted the data in the object that A originally pointed to, but it didn’t. You are simply removing the address reference from the object, you are not deleting it, JS monitors unreachable values and removes them through the garbage collection mechanism.

That is, if you have a=null, the previous object will not be deleted immediately, but J will do so at an appropriate time. Also, a=null does not mean that the object has lost the address reference. It is possible that you have kept the address reference in another variable somewhere else, and the desired effect will not work.

7. Let’s talk about closures again

There are a lot of concepts that seem to have nothing to do with closures, but it’s these concepts that will allow you to fully understand closures and use them to solve practical problems.

7.1 Variables resident in Memory

According to the terminology provided by MDN, in JS, the time when a closure is generated is when the function is created. But I still don’t really know what it means, so I have to look at what others understand and find a sentence that I think I can understand:

Closure is the ability to create a separate environment, each inside the closure of the environment is independent of each other. Closures can leak memory, creating a new address every time an external function executes at a different reference address. However, any data in the current active object that is referred to by the internal subset is not deleted. A pointer is reserved for the internal active object.

Original source link

Master Peng never plagiarizes, nor does he lie.

When an internal function references data from an external function, the data is not deleted. Instead, a pointer is reserved for the internal object, so that the memory space of the data is not drained by the reclamation mechanism.

7.2 Higher-order functions: the realization of throttling and anti-shaking.

So let’s analyze one of the two functions we are often asked about: throttling and anti-shaking.

These two functions are mentioned together, and their concepts are often confused, sometimes confusing the difference between throttling and anti-shock, and the scenarios in which they are used.

  • Scenario 1: I just have a concept in mind. When I want to optimize some operations, THROttling and anti-shaking will come to mind, but I am not sure which one to use. I need to search again and then choose one.
  • Scenario 2: During the interview, the interviewer always asks you to handwrite the two higher-order functions of throttle and anti-shake. You always memorize before the interview, and when it comes to the written test, you always forget how to write, or you always miswrite some steps.

So, in this section, we’ll take a thorough look at the concepts of throttling and stabilization, how to use them in real code, and how to write one yourself.

We do not start from what is throttling and anti – shake these two concepts, so it is still a new concept, not easy to understand.

Let’s take these two concepts out of the actual application and make them your own.

7.3 Search Box Optimization

The first round:

One day, you develop a search box feature for keyword retrieval.

You check it out, check it out, hand in the code, and happily let the test go, thinking it’s seamless and bug-free.

Soon test little sister said, you have a bug this page ah.

?????

You think, how is it possible, I clearly tested many times, there is no problem, you tell me, where there will be a problem?

Test little sister: you this input box, I just input a word, I haven’t input over, this page how to begin to have search results?

First Blood!!

Well?? It must be my little sister’s slow typing. I’ll go and see what’s going on.

If you look at the code, it turns out that you are using the input, change event, which is triggered when the value of the input binding changes and then invokes the search interface.

However, it seems that there is no need to search as soon as there is a value, so the first problem is that the user will query the results before they have finished typing. The second problem is that constantly requesting the interface creates unnecessary requests, resulting in performance degradation.

Then, as a qualified programmer, you should think about how to optimize the search function. Follow the conventional way of thinking, or I add a button next to the input box, click to search, so that there will not be not finished typing the start of the search problem, also will not constantly request the interface.

The second round:

So you, uh, start a new round of plumbing. Crackling, half an hour later, you are happy to submit the code, and then the little sister hands.

Before long, the little sister told you, there is a problem again, your interaction logic is very cumbersome ah, the user input, but also to click the ugly button, very inconvenient. Second, I stress test, I can keep clicking the button, so I keep sending HTTP requests, causing network congestion, if someone deliberately break the damage, keep requesting background, will bring serious impact on the server performance and bandwidth.

At the moment, your heart is broken, I clearly optimized, how seems to be more problems.

Double kill!!!

After a period of self-doubt and self-adjustment process, you have a new optimization plan, I will limit the frequency of your user request interface, I will let you delay for a while, will trigger the call interface, is not bad!

The third round:

You start by writing a setTimeout function at the point where the interface is called, which delays calling the interface by one second. You test it a few times and feel there is nothing wrong, carefully check it a few times and then submit the code to the test sister.

After a little sister knead abuse, little sister said: you still can’t ah, although the first time is delayed 1 second request, but I point 100 times, but after 1 second, immediately there are 100 requests.

Triple kill!!!

The fourth round:

You’re on the right track, but it’s not working. First, make sure that it is possible to use the delay method, but how to limit the request to one request at a time. For example, if you set a time of 2 seconds, during which 10 chang events might fire, how can you fire 10 chang events only once?

Point to ponder: How can 10 requests only trigger once?

The idea, start with the timer itself. What is a timer?

setTimeout((a)= > {
    console.log('If you're a boy on the screen, you're handsome. Girls, the skin white beauty! ')},1000)
Copy the code

After the above code runs, after about 1 second will output:

Screen before you, if the boy, handsome; Girls, the skin white beauty!

This sentence.

If we wrap it in a function:

function sayToYou() {
    setTimeout((a)= > {
        console.log('If you're a boy on the screen, you're handsome. Girls, the skin white beauty! ')},1000)
}
sayToYou()
Copy the code

Is it going to be the same.

So if I click the button 10 times, it will trigger the sayToYou function 10 times, but after a delay of 1 second, it will send out 10 sentences immediately.

What if I want the sayToYou timer callback to be triggered after 10 quick clicks in a row?

If we can use a timer variable will store up first, if in a period of time (the first timer timing) within the limits of time and have a new function is triggered, then figure out whether the variable is the first, if there is on a timer variable (the first), then I will be on a timer (first) remove, don’t let it triggered the callback. So the previous click event is not valid. The callback event will be triggered only if the current click event continues to enter the timer and waits for this period to pass.

So let’s do this and modify our sayToYou function:

let timer = null
function sayToYou() {
    if(timer) {
        clearTimeout(timer)
    }
    timer = setTimeout((a)= > {
        console.log('If you're a boy on the screen, you're handsome. Girls, the skin white beauty! ')
        clearTimeout(timer)
    }, 1000)}// Click the button 100 times in a loop
for(let i = 0; i < 100; i++) {
    sayToYou()
}
// After a second or so, output only once.
Copy the code

Run the code a second time and you’ll be in tears, because the result is exactly what you expect.

First we mask the clear timer line and get 100 sentences:

Then unblock the clear judgment:

Is it just one sentence as we expected?!

So let’s go ahead and optimize our code

Now, we can only trigger one callback at a higher frequency, but we also need to declare a variable, so what if I need to call this function in many places?

Wouldn’t it be troublesome to define a variable in multiple places?

So, having said so much and introduced so many JS concepts, we are finally coming to the big game. When we discussed closures in front of us, did we talk about the advantages of closures and whether we can use them?

Now what are the advantages of closures?

Answer: Make variables resident in memory

Ok, so let’s introduce the closure feature and modify it:

  • 1. First, we need to create a closure
  • 2. Internal functions use variables from external functions
  • Return this function

The code after transformation:


function debounce() {
    let timer = null
    
    function sayToYou() {
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout((a)= > {
            console.log('If you're a boy on the screen, you're handsome. Girls, the skin white beauty! ')
            clearTimeout(timer)
        }, 1000)}return sayToYou
}

// Use a variable to get a shake-proof return function
const sayToYou = debounce()

// Click the button 100 times in a loop
for(let i = 0; i < 100; i++) {
    // Execute this function
    sayToYou()
}
Copy the code

The sayToYou function has not changed a single line of logic. What is it?

  • 1. Create another function called debounce, which defines a timer variable.
  • 2. An internal function sayToYou, which is the sayToYou function we used earlier. (No change)
  • 3. Finally return this function.
  • 4. The method of reference changes. The externally called sayToYou function returns the value obtained by calling debounce.

“Debounce,” he said. “sayToYou,” he said, “is not so good at remembering things.” The father god needs to mark the child with a line on his arm. The process of making a baby is very simple. The father god grabs a handful of mud and starts pinching the baby, then strokes it on his arm. (It takes a second to make a baby), and when he is finished, he waves his hand and the marks on his arm disappear.

// When the father god is executed, it is the time for the father God to start making babies, and miss will urge the baby formula lost in the world
constSayToYou = debounce()// At this time some foolish mortals have been praying to heaven.
// For this formula to work, two mysterious symbols must be added ()
for(let i = 0; i < 100; i++) {
    / / EvaSayToYou ()}// Although this time was urged baby 100 times, but the father god saw the traces on his hands still ah, you urge a ball baby, wait for this god.
// Finally, after the baby was finished, the father god waved his hand, the traces of his arm disappeared, and the baby fell to the ground.
Copy the code

The results

That’s how we use closures to optimize our function.

7.4 Understand closures thoroughly

In fact, you may still not understand the meaning of this sentence.

“Each time an external function is executed, a new address is created with a different reference address. However, any data in the current active object that is referred to by the internal subset is not deleted. A pointer is reserved for the internal active object.

This sentence is so long that we break it into two sentences:

  • 1. Each time the parent function is executed, a new reference address is created.
  • 2. A data pointer is reserved for use by internal functions.

When combined, it means that the variable referenced by the same internal function referring to the same address is the same pointer.

To visualize the difference, we’ll change the code:

Execution process: A gets an address reference to the parent function execution, and at the same time, obtains the pointer to I after the parent function execution.

Execute function A 3 times:

    1. i = 0 => i++ => 1
    1. i = 1 => i++ => 2
    1. i = 2 => i++ => 3

So here’s another example:

We wrote one more b function, and then we executed it twice a, once B.

I get 1, 2, 1

What does that tell us?

Each time an external function is executed, a new address is created if the reference address of the external function is different.

So since A and B refer to different addresses, they each have a closure variable I (different addresses)

So after a is executed twice, the internal variable I in the address referenced by A is 2, and then b is executed. Do not think that I is 2. In fact, the internal variable I in the address referenced by B is 0, so the output after b is executed is 1.

1 console.log(a === b)

So, let’s change the above code slightly:

What does the console output at this point? Do you know the answer?

Think 2 console.log(a === b)

7.5 Then your anti – shake function seems to be different from the market! ?

So in this section, we will continue to optimize our anti – shake function, try to make it look like you want!

The anti-shake function on the market seems to be used like this:

const sayHello() {
    console.log('hello')}const a = debounce(sayHello, 1000)
Hello / / output
a()
Copy the code

Why is that so hard? We understand the nature of closures almost, pass in two parameters, no problem!

Let’s see, the first argument is the address of a function, and the second argument is the delay time.

// Naruto 1 generation
function debounce(fn, delay) {
    let timer = 0

    return function () {
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(fn, delay)
    }
}

const sayHello = (a)= > console.log('hello, every body! ' )
const a = b = debounce(sayHello, 1000)

a()
a()
b()
Copy the code

We set two parameters, and it looks like the market version, with just a welcome message.

But what if my sayHello function doesn’t print a sentence, but instead passes a name to it when called?

Huh? It seems that we can continue to optimize:

// Naruto 2
function debounce(fn, delay) {
    let timer = 0

    return function () {
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout((a)= >{ fn(... arguments) clearTimeout(timer) }, delay) } }const sayHello = name= > console.log(`hello, ${name}! `)
const a = b = debounce(sayHello, 1000)

a('jcok')
a('lili')
b('cat')
Copy the code

Changes:

  • 1. Here we’ve modified sayHello to support passing in a name and output a welcome message.
  • # sayHello; # sayHello; # sayHello; # sayHello; Some people may be strange to this argument, and others are curious about… The arguments that the syntax of the confused, here recommend you to practice hand, print the arguments will probably see a few times more. And deconstruction is three dots… This section belongs to ES6.

In the end, we thrashed the name jock, lili, and cat three times, and only printed the last one triggered within 1000 milliseconds, that is, hello, cat!

Let’s run it:

In fact, it turned out exactly as we wanted.

Do you think that’s it? No, actually there is a “this” pointer missing from the market version.

The sayHello function is mounted on the window object by default, so it works fine without fixing the pointer in fn.

However, the anti-shake function is used in many places, and if you don’t correct the “this” pointing correctly, you may have bugs.

Here’s a practical example:

function debounce(fn, delay) {
    let timer = 0

    return function () {
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout((a)= >{ fn(... arguments) clearTimeout(timer) }, delay) } }const obj = {
    name: 'god'.sayHello: function () {
        console.log(`hello, The ${this.name}! `)}}const a = debounce(obj.sayHello, 1000)
a() // hello, undefined!
Copy the code

Modify points:

  • 1. Write the sayHello function to an object obj. (Note that arrow functions can no longer be used.)

You’ll notice that when you run the program, the output is hello, undefined! (The test is performed in a NodeJs environment, but there are no Documents and Windows in NodeJs.)

If you don’t believe it, run obj.sayHello() on its own and you get hello, God!

The window object has no name, so it will print hello, undefined!

If you don’t believe me, we’ll mount a name property called John on the window and try it out.

Leave the code for Debounce and obj unchanged:

1.先执行一次debounce(obj.sayHello, 1000)()

The result is hello,! I was stunned for two seconds and realized that the window object already had a name attribute of “”, so I printed window:

If we print sayhHello instead of name, but instead of a property that does not exist on the window, we should print hello, undefined!

2. Then change the window.name property to ‘John’.

Calling debounce(obj.sayHello, 1000)() gives us the same result as we predicted: Hello, John!

Therefore, the conclusion is consistent with our conjecture. Finally, we need to modify the direction of this.

7.6 A relatively complete anti – shake function is born

function debounce(fn, delay, _this) {
    let timer = 0
    return function () {
        if(timer) {
            clearTimeout(timer)
        }
        // This sentence is crucial
        const that = _this ? _this : this
        timer = setTimeout((a)= > {
            fn.apply(that, arguments)
            clearTimeout(timer)
        }, delay)
    }
}
const obj = {
    name: 'god'.sayHello: function () {
        console.log(`hello, The ${this.name}! `)}}const a = debounce(obj.sayHello, 1000, obj)

a() // hello, god!
Copy the code

Note here that we must actively pass this obj as the current context to fn to apply this, otherwise even if we use fn.apply(this, arguments) to correct this, the result will still not be what you want.

const that = _this ? _this : this
Copy the code

If you do not explicitly pass in a context, the current context is used by default.

In other words, the current context of the environment in which Debounce’s return is finally executed is passed as the default context to that, and then the fn’s this pointer is corrected. (Without actively passing in a context)

A lot of people here will be confused, so you can try it.

7.7 A more complete test case

function debounce(fn, delay, _this) {
    let timer = 0
    return function () {
        if(timer) {
            clearTimeout(timer)
        }
        const that = _this ? _this : this
        timer = setTimeout((a)= > {
            fn.apply(that, arguments)
            clearTimeout(timer)
        }, delay)
    }
}

const obj1 = {
    name: 'obj1'.sayHello: function () {
        console.log(`hello, The ${this.name}! `)}}const obj2 = {
    name: 'obj2'.a: debounce(obj1.sayHello, 1000)}const obj3 = {
    name: 'obj3'.a: debounce(obj1.sayHello, 1000, obj1)
}
const a = debounce(obj1.sayHello, 1000)
const b = debounce(obj1.sayHello, 1000, obj1)

obj1.sayHello() // Do not use anti-shake optimization

obj2.a() // The default context is obj2

obj3.a() Explicitly change the context to obj1

a() // The default context is Window (in the browser environment).

b() Explicitly change the context to obj1

Copy the code

You can test the above code to see if the output matches what I analyzed above.

8 summarizes

In a word: JS broad and profound, or is I snack generation can covet! ?

The latter

I am xiaodai, you are welcome to discuss technology with me.