You need to understand scope before you can understand closures, and you can’t talk about scope without mentioning JavaScript compilation

compile


Generally speaking, languages fall into two types: compiled and interpreted languages. These languages generally refer to code we write (i.e., high-level languages) that machines can’t read and therefore need to be translated into machine language before it can be executed.

The biggest difference between the two languages lies in the different translation timing. In compiled languages, the code is compiled into a machine language file in advance, and then the compiled file can be directly run during the runtime. There is no need to compile repeatedly during each run, and compilation and running operations are relatively independent (such as C/C++). Interpreted languages also need to be compiled before they run, but the compilation phase usually takes place just before execution, usually immediately after compilation.

Var a = 1; var a = 1; var a = 1;

  1. The compiler takes the code and splits it into undivided lexical units, such as var, a, =, and 1.
  2. Parsing/parsing is too messy after dismantling! It is necessary to sort out these lexical units and transform them into a Tree representing the Syntax structure of the program, called Abstract Syntax Tree (AST), which is composed of elements nested step by step.
  3. I (the compiler) looked comfortable with the code generation, but couldn’t execute it, so I had to convert the AST into executable code, which could be interpreted as machine code.

Ok simple understanding of the compile operation, the actual operation is much more complex than said here, during the period also includes a lot of performance optimization and other operations, nothing here we only need to understand these major steps.

Simply put, any snippet of JavaScript code is compiled before execution (usually just before execution). Therefore, the JavaScript compiler first compiles the program var a = 1, then prepares to execute it, and usually executes it immediately.

Understanding scope


What does a scope do

Now that we know that there is compilation, let’s go back to var a = 1; How exactly is this statement executed?

In the compile phase, the compiler will partition the code string into an abstract syntax tree. Finally, in the process of generating code, the compiler will see the declaration of the variable A, the first question will be asked if the scope of the variable a exists. If so, the compiler ignores the declaration and continues compiling. Otherwise the compiler will ask the scope elder to declare a new variable in the collection of the current scope and name it a. (If there are multiple declarations in the current scope code, they will be declared in advance at compile time, also known as variable promotion.)

The ok compiler is done and sends the generated machine code to the JS engine to execute. Since it has declared it, the engine sees var a = 1; “, it knows that what it should do is to assign a value of 1 to a, so it should first ask the scope eldest brother, there is a variable a, the eldest brother dug down his trouser pocket found a and handed over a, js engine after getting the value of 1; If the js engine has no other scope to ask for, it will only throw an error.

Scope nesting

As mentioned in the above example, “There are no other scopes to ask.” Yes, there can be many scopes, either nested or co-existing. Look at the following code

function foo () {
    var b = 2;
    console.log(a + b);
}

var a = 1;
foo();
Copy the code

There are two scopes, one global and one for Foo. Again, after compiling, execute foo(); “, first encountered variable b, ask foo eldest brother b, some good assigned 2; . “> < div style =” box-sizing: border-box; color: # 33ff0; Ok, I found it in the global scope brother, I get a value of 1, I print it.

Looking at the small examples above, you can probably see that the big scope brother is mainly managing its own stuff; This kind of nested by a number of scopes, called the scope chain, the top layer is the global scope, the rule of finding variables is along the scope chain layer by layer to find.

So how does the scope chain come about? The answer is that it happens at compile time, which is when you write code. Why is that? Lexical scope specified.

Lexical scope

First of all, scopes are not specific to JavaScript; they are a set of rules that govern how the engine looks up by identifier names (variable names) in the current scope as well as in nested scopes.

There are two main working models of scope: the first is the most common, lexical scope adopted by most programming languages (js is the one), and the other is called dynamic scope, which is still used by some programming languages (Bash scripts, some patterns in Perl, etc.).

Simply put, lexical scope is the scope of a definition at the lexical stage. In other words, the lexical scope is determined by where you write the variable and block scopes when you write the code, so the lexical analyzer keeps the scope the same when it processes the code (most of the time, except eval, with, etc.). There’s no reason why, because that’s how lexical scopes are defined, which is why we often say that scopes are defined at compile time, when code is written.

closure


What is a closure

Balala said so much, the closure is finally here. First, a closure can also be understood as a permission to access a scope. Closures are also a natural consequence of writing code based on lexical scope.

function foo() { 
    var a = 2; 
    function bar() { 
        console.log( a );
    } 
    return bar 
} 
var fn = foo();
fn(); / / 2
Copy the code

Executing the result above gives us output 2. Analyze it. First, there are three scopes: global, foo, and bar.

When performing fn (); If the bar function is not allowed to use a variable, the bar function is not allowed to use a variable. If the bar function is not allowed to use a variable, the bar function is not allowed.

If you look back at the code, if you focus on fn(); For a split second, you might think global scope is next; If you look at the bar function definition, you might think that foo scope should be asked next. Yes, this is also the difference between “dynamic scope” and “lexical scope” mentioned above. Then pay attention! JavaScript uses lexical scope! So the next thing to ask is foo’s scope brother, because the chain of scopes is already generated at compile time, according to the rules of lexical scope, so look at fn(); It’s called in the global scope, but it has access to the Foo scope in its function body, and that’s its permission, and that’s the closure.

In other words, when you parse the above code strictly by the rules of lexical scope, even if you don’t know the name closure, you know what it prints out as a 2, and you know what the scope chain looks like, but we’ve given this seemingly magical thing a name called closure. Hence “closures are also a natural consequence of writing code based on lexical scope.”

Not to get too convoluted, but to summarize, the ability to access other scoped things in the current scope is the ability of closures.

In addition, closures can increase memory footprint. Under normal circumstances, after a function performs the memory it occupies garbage collection can be recycled and release the memory, but there are some different for closure, as foo function after the execution, this should be garbage collected, however, due to the fn maintained for foo scope references, so foo would not be in the execution of the recycled.

How do YOU make closures

Closures occur when a function is not called in a scope that defines its own lexical scope.

For example, the above example returns a function within a function and is called in the global scope; Also, pass a function into another function for a call:

function foo() { 
    console.log( a );
} 
function bar(fn) {
  var a = 2;
  fn(); // Closure is generated here, foo is not called in its own lexical scope
}
var a = 1;
bar(foo); / / 1.
Copy the code

In addition, setTimeout, which we often use, can also generate closures

function wait(message) { 
    setTimeout( function timer() { 
        console.log( message ); 
    }, 1000 ); 
} 
wait( "Hello, bibao!" );
Copy the code

The timer function retains access to the wait scope wherever it is called inside setTimeout, so it prints message correctly.

Finally, what can closures do

One of the biggest uses is modularity with closures. Before ES6’s block-level scope, function scope was often used to encapsulate modules, as shown in the following example:

function utilsModule(){
    var name = 'util';

    function getName(){
        // ...
        console.log(name); 
    }

    function format(){
        // ...
        console.log('format'); 
    }

    return {
        getName: getName
        format: format
    }
}

var utils = utilsModule(); // Get the module

// Use API exposed by module
utils.getName(); // util
utils.format(); // format

Copy the code

In the utilsModule, we encapsulate a utility module, utilsModule, in which we return an object whose properties are the API we want to expose to the outside world. UtilsModule () is called to utilsModule() to get a reference to utils. UtilsModule () invokes the required API, which generates closures. Closures are common in our code.

Encapsulation avoids global contamination and is easier to maintain and expand.