Recently, I read JavaScript you Don’t Know (part 1), and the following is all about the book. It is also a summary (or note) of what I have read.

How JavaScript is compiled

Yi! Isn’t it going to scope? Why are we talking about compilation principles? Don’t worry, after the principles of compilation, it will help to understand the concept of scope

JavaScript is a function-first, just-in-time scripting language, meaning that when we write JavaScript code to run, we need the runtime environment to compile it into machine language

Compilation is the process of converting a source language into a machine – aware binary language. Compiling is divided into five stages: lexical analysis, syntax analysis, semantic check, intermediate code generation, code optimization, and object code generation.

The compilation process of JS is also roughly through the above mentioned several processes:

  1. Lexical/lexical analysis is the process of breaking the code we write into meaningful blocks of code (called lexical units). For example, if we write var a = 2, it will be broken into var, a, =, 2, which is easy to parse
  2. The parse/parse phase converts the lexical units decomposed in the previous step into an abstract syntax tree (AST). Each lexical unit corresponds to a node in the syntax tree, and each node has a type to represent the type of the lexical unit
  3. Code generation converts the abstract syntax tree into a machine language that is recognized by the current environment.

In the JS compilation process will also have the corresponding performance optimization steps, if you are interested in their own research.

We have briefly explained the compilation process above to better understand the concept of scope, let’s take a look at what scope is.

scope

Scope is a set of rules for storing (by what rules) and finding (where to look when they are used) variables.

Understanding scope

Understand scope? What you mean? That is, understand how scopes store (collect) variables and what rules they use to find them

Var a = 2; var a = 2; var a = 2;

  • Declare variable A

    First, the compiler (which is responsible for parsing and code generation) declares a variable with the identifier a in the current scope. (It looks for the variable in the current scope and does not declare it again.)

  • Assign a value to the variable A

    This step is executed at runtime. The engine (which is responsible for compiling and executing the JS code) will ask the scope if there is a declaration with the identifier a, and if there is, it will assign it a value of 2. If not, it will throw an exception (ReferenceError).

Var a = 2: the compiler asks if the scope has already declared a variable. If not, the compiler declares a variable in the current scope. If so, the compiler merges the declaration. The engine executes code a=2, looks for the variable in scope, assigns it if it can be found, and throws an exception if it can’t be found

The above two steps illustrate the process of collecting and finding variables, which is where scope comes in.

LHS and RHS lookup

When the code is generated during compilation, the engine executes the code to find out whether a is declared or not, depending on the scope. There are two types of lookups: LHS lookup and RHS lookup

  • LHS lookup

    Where does the engine verify that the variable is declared so that it can be assigned

    Or take the example of the var a = 2: met a = 2, while engine code execution engine will ask the current scope, is there a statement? (Will also go to the outer scope query…)

    In other words, it doesn’t care what the value of a is, the engine just wants to make sure where A is declared, and then assign 2 to it.

  • RHS lookup

    It means that the engine wants to know what the value of this variable is, so it can use that value to do something else. Right

    So let’s say we just assigned a value of 2 and b is equal to a and when the engine gets to this code it’s going to do an LHS lookup on B, and then it’s going to do an RHS lookup on A, and what the engine wants to know is what is the value of a, and it’s going to assign that value to b, right

    When a is assigned to another variable, an RHS lookup is performed to determine what the value of a is, which can be interpreted as: get its source value.

Let’s understand the process of two lookups in terms of a piece of code:

function foo(a){
  var b = a 
  return b+a
}
var c = foo(2)
Copy the code

The execution of the above function should look like this:

  • Engine: scope help me find if c is defined in you, (LHS)
  • Scope: Yes, just defined by the compiler,
  • Engine: Could you please check if foo has been defined? (RHS)
  • Scope: Yes, that was defined too.
  • Engine: Let’s see if a is defined. Brother, (LHS)
  • Scope: Yes, the compiler just defined a function parameter a
  • Engine: Ok I’m going to assign 2 to a now
  • Engine: I found another variable B, could you please check again? (LHS)
  • Scope: Yes, you have a declaration in the context of the function being executed
  • Engine: A~ I see this A again. Please check if it is the one I just assigned. (RHS)
  • Scope: Ok, boy, don’t worry, that’s the thing.
  • Engine: Brother, here I am again. This time I met A in another place. Please help me to confirm whether it is him or not. (RHS)
  • Scope: little brother, the last time ah, yes or he, the function inside is he.
  • Engine: Ok, A~, I met B again, brother this time you still help? (RHS)
  • Scope: Oh, you’re a pain in the ass. Yeah, that’s the same B
  • Engine: Brother, I am so relieved to have you here. Ok, I have confirmed that I am ready to start work… I’m going to assign the sum of these two values (a,b) to c, and THEN I can rest.
  • Scope:…

Scope nesting

When a variable cannot be found in the current scope, the engine continues to search in the outer nested scope until it finds the variable or reaches the outermost scope (global scope).

Lexical scope/dynamic scope

Now that you have an idea of scope, let’s look at two new nouns: lexical scope and dynamic scope

Lexical scope

Lexical scope is the scope defined at the lexical stage, that is, it depends on which function scope or block scope we define the variable in when we write code. In most cases, this is constant (evel/with will change).

Here’s a picture from the book to make it clearer:

  1. Contains the global scope, including the identifier foo
  2. Contains the scope created by foo, including the identifiers a,bar, and b
  3. Contains the scope created by bar, including the identifier C

No matter where a function is called, its lexical scope is determined only by where the function was declared.

Dynamic scope

Dynamic scope means that when you look for identifiers you don’t look for them in lexical scope nesting, you look for them in call order, which means that dynamic scope doesn’t care where you declare them, it only cares where you call them.

Here’s an example:

function foo(){
  console.log(a)
}

function bar(){
  var a = 3 
  foo()
}

var a = 2 

bar() // 3 is not 2

Copy the code

If you look at the code above, the actual output of foo is 3, not 2. Foo is created in the global scope, which means that if you look for a variable a in the scope nested rule, it should be 2, but in fact it’s 3 in the bar scope, which means that when you look in the dynamic scope, you don’t care where it’s defined, it’s based on the call stack.

conclusion

Lexical scope is determined at code writing time, and dynamic scope is determined at run time. Lexical scope is concerned with where functions are declared, whereas dynamic scope is concerned with where functions are called

closure

Closures occur when a function can remember and access the lexical scope in which it is located, even if the function is executed outside the current lexical scope

There are many concepts to describe closures, but here’s the code to explain what a closure is:

function foo(){
  var a = 2
  function bar(){
    console.log(a)
  }
  bar()
}
foo() / / 2

Copy the code

Is the above example a closure? I think so, because bar accesses data in the scope of Foo, and of course access variable A through the lexical scope nested lookup rule we mentioned above

The above example does not show that the function is executed outside the scope of the current lexicon, so let’s look at another example

function foo(){
  var a = 2 
  function bar(){
    console.log(a)
  }
  return bar
}

let baz = foo()
baz() / / 2
Copy the code

Foo () returns an internal bar function, which is assigned to baz and called baz(). The actual procedure is to call bar(), but to assign the function to several different identifiers. This example clearly shows that bar is executed outside the defined lexical scope

Tip: Foo is normally destroyed after execution, but bar is not immediately recycled because it has a reference to a variable in its scope

No matter how you pass a value to a function type, the function is understood to be a closure when it is called in another scope.

function foo(){
  var a = 2 
  function bar(){
    console.log(a)
  }
  return bar
}

function baz(fn){ 
  fn() // This is the closure
}

baz(foo())

Copy the code

Let’s use the classic for loop example to illustrate the use of closures:

for(var i=1; i<=5; i++){setTimeout(function timer(){
    console.log(i)
  },0)}/* we expect to print 1,2,3,4,5, but it's actually 6,6,6,6,6,6,6. Even though I set the delay to 0 seconds, the callback function in setTimeout will still execute after the end of the loop (see macro and microtask), when I =6, When the callback timer executes, it looks for I with a value of 6, so it outputs five 6's so if I generate a closed scope is that ok? * / 
/* Create a closed function scope with IIFE */
for(var i = 1 ; i <= 5; i++){
  (function IIFE(){
    setTimeout(function timer(){
      console.log(i)
    },0()})})/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
for(var i = 1 ; i <= 5; i++){
  (function IIFE(j){
    setTimeout(function timer(){
      console.log(j)
    },0)
  })(i)
}
/* We create a closed scope for each iteration, but we can also use the let declaration to generate block-level scopes, */ 

Copy the code

Summary and reflection

The above is my own understanding of scope and closure, may be written in some places is not strict or error, welcome to comment.