Please indicate the original link for reprinting. The original link

The handwritten interview questions series is a series of essays I wrote to prepare for the present and future interviews, of course, it is also helpful for front-end partners. I recommend typing the code yourself or writing it by hand to get a better grasp of it.

References:

  1. Closure – MDN
  2. Lexical Environment — The hidden part to understand Closures
  3. Understand the execution context and execution stack in JavaScript

At the end of the last article, I explained the concept of lexical context and also referred to closures, but did not expand to explain them clearly. In this article, I’ll take a peek at closures.

What is a closure?

First take a look at MDN’s official explanation:

A combination of a function bound to (or surrounded by) references to its surrounding state (lexical environment) is a closure. That is, closures allow you to access the scope of an outer function within an inner function. In JavaScript, whenever a function is created, the closure is created at the same time the function is created.

1. Personal interpretation

First, let me analyse the implications of the official explanation. Start with the first sentence: a combination of a function bound (or surrounded by) references to its surrounding state (lexical environment) is a closure.

To simplify the first statement a bit, a function combined with references to its lexical context is a closure. That is: closure of function fn1 = fn1 + a reference to the lexical environment of fn1. We know that once a function is defined, its lexical context is already defined, so a function must have closures.

In other words, closures allow you to access the scope of an outer function in an inner function.

When I first encountered the concept of a closure, I thought of an inner function that accesses the scope of its outer function as a closure. Closures, however, have one property: they allow you to access the scope of an inner function.

Last word: in JavaScript, whenever a function is created, the closure is created at the same time the function is created.

The last sentence, which supports my explanation of the first one, is that closures are generated whenever a function is created.

Conclusion 2.

The above paragraph is just to show the reader what closures really are. Since I was wrong about closures in the past, I spent some time explaining them in order to avoid readers getting the same idea.

Here, we conclude the official meaning of closures:

  1. Closure = function + reference to function lexical environment;
  2. Closures have the property that you can access the scope of an outer function in an inner function;
  3. A closure is created when a function is created, meaning that as long as the function is defined, the closure must be generated, regardless of whether it accesses the scope of the outer function.

Lexical environment and closure of functions

The property of closures is that inner functions can access the scope of outer functions. To understand why closures have this feature, you have to mention the concept of lexical environments.

Lexical environment consists of two parts, one is the environment logger, and the other is the reference to the external environment.

Environmental recorder is the actual location of storage variable and function declarations, more specific point is the storage function of variable, function, and parameter list (the arguments object, can put the function parameters as the variables, functions declared within the parameter to a function parameter by value is to give, but still belongs to the scope of the function parameters function); A reference to an external environment means that it has access to its parent lexical environment (scope);

In layman’s terms, in a lexical context, the context logger records the actual values of variables and functions in the execution context, and references to the external context allow that execution context to access the parent scope along the scope chain.

This is expressed in pseudocode:

LexicalEnvironment: {// Environment logger: records a list of variables (let, const), functions, and arguments defined inside a function. EnvironmentRecord: {Type: Outer: <null>} outer: <null>}Copy the code

Because the lexical environment of a function stores references to the external environment, it allows the function to access variables and functions in its outer scope. Since the lexical environment of a function is defined at the time of function definition, the level of the external environment that a function can access is defined at the time of function definition, which can be interpreted as: the level of the external environment that a function can access is defined at the time of function definition, and does not change because of the way the function is called.

This sentence is convoluted and can easily be confused with the fact that the direction of this depends on how the function is called, which takes a while to understand and digest.

Here’s a simple example:

const num = 10; function fn1() { console.log(num); } function fn2() { const num = 20; fn1(); } fn2(); / / output 10Copy the code

In this example, although function fn1() is called inside function fn2(), the output num is still the value of num in the global environment; Changing the way fn1() is called does not change the level of upper scopes that fn1() can access, because these things are defined when the function is defined.

Common use examples of closures

The nature of closures allows functions to access outer scopes. If an inner function accesses a variable in the outer function, the outer function has finished executing, but the inner function wants to access the variable in the outer function, and the variable is not reclaimed until the inner function has finished executing.

1. In the timer
(function autorun() { const num = 100 setTimeout(function log() { console.log(num) }, 1000) console.log(' Autorun completed ')})()Copy the code

The command output is autorun 1000 completed

One second after autorun completes, the internal function log outputs the value of num 100. That is, after autorun() completes, log() still has access to num in the outer function Autorun ().

2. In event processing
(function autorun(){ const num = 100; $("#btn").on("click", function log(){ console.log(num); }); Console. log(' Autorun completed ')})();Copy the code

Same output. Since the click event is also delayed, the log() function is not called until autorun() has finished executing.

In the two examples above, we can see that the log() function can access variables in the inner function even after the outer function Autorun () has finished executing.

If an inner function accesses a variable in an outer function, the lifetime of the variable depends on the lifetime of the inner function. Variables in the outer scope referenced by the inner function will survive until the closure is destroyed. If a variable is referenced by more than one inner function, it will not be destroyed until all the inner functions have been garbage collected.

Closures and loops

When an inner function accesses a variable in an outer function, it accesses a reference to the variable in the outer function, but does not copy the value of the variable. As used in loops:

(function initEvents(){
  for(var i=1; i<=10; i++){
    setTimeout(() => {
      (function showNumber(){
        console.log(i)
      })() 
    }, 100);
  }
})()
Copy the code

The showNumber() function is executed after the for loop is complete, so I is incremented one last time. I is 11. Since I is defined by the var keyword, it is promoted to the global scope by the variable, so showNumber always gets the same reference to I, so the result is the same. Plus the loop will run again after I = 10 I ++, so 10 times the output is the same, 11.

In this case, if you replace var with let, each I is defined separately, and each showNumber references a different I, so the output will be 1-10.

4. Performance considerations for closures

It is unwise to create functions in other functions that do not require closures for some specific task, because closures have a negative impact on script performance in terms of processing speed and memory consumption. Due to the nature of closures, it is possible to access the outer functions, but this operation requires going up the scope chain, causing unnecessary consumption.

In addition, because the inner function accesses the variables in the outer function, the variables in the outer function will not be destroyed before the execution of the outer function is complete, so that the storage space can not be reclaimed, resulting in performance problems.

So you shouldn’t use closures as much as you need to.