Writing in the front

In JS, Scope (Scope) of knowledge has been abstract and difficult to understand, the front end of the small white is a graduate from college, the writer in the study Scope and in the process of the Scope chain is also a tormented, this article will as much as possible with popular words to explain the Scope and the Scope chain, hope to help struggling in the Scope of the junior, If there is any misunderstanding or not in place, please also point out, appreciate ~ (the pictures without attribution are from the network, deleted)

1. Compilation principle

This article is clearly about scope, why should we talk about compilation principles first? Trust me, understanding how compilation works is a huge help in understanding scopes.

In traditional compiled languages, a program executes code in the following three stages:

  • Word segmentation/lexical analysis
  • Parsing/parsing
  • Code generation

Lexical analysis

Or word segmentation, the first thing you have to understand is that the browser can’t read your code, it can only read binary code, so it can only split your code. Here’s an example:

    var a = 2;
Copy the code

How does the browser do that? The browser first breaks this code down into lexical units: [var, a, =, 2,;] .

Syntax analysis

The process of parsing is to convert the lexical unit stream (that is, the array at the end of the previous step) into a hierarchical nested Tree of elements that represents the deconstruction of the program’s Syntax, called the AST (Abstract Syntax Tree). (In Chinese) Don’t panic, here is a website called Jointjs, as shown below:

Code generation

We won’t go into the details (I won’t), but simply put “Var a = 2;” The AST of this line of code translates into machine code that the machine can run. Here’s how Chrome v8 works:

Interested friends can go to see Teacher Li Bing “Browser working Principle and Practice”

2. What is scope

Scope is the accessible scope of variables and functions, or limit their available scope. Scope determines the visibility of variables and other resources in the code block. The use of scope can effectively improve the locality of program logic, enhance the reliability of the program, and reduce name conflicts. (In Chinese)

Is there a way that you know every word, but you can’t read it together…

Here’s an example:

function fn (){
    var b = 0
    console.log(b) / / 0
}
fn()
console.log(b) //b is not defined
Copy the code

The above example shows the basic concept of scope. The variable b is a variable in the function fn and is not declared in the global scope. Therefore, it can only be referenced in the function scope of fn.

In other words: B lives in the FN community, if you shout in the FN community :” B you come out!” If you shout louder, he will hear you and come out, but if you shout outside the neighborhood, some kind passer-by will come and tell you that there is no such person (error!

Before ES6, there was no concept of block-level scope in JS, only global scope and function scope. The new lets and const in ES6 provide the concept of “block-level scope”.

1. Global scope and function scope

Global scope as the name suggests, objects with a global scope can be called from anywhere, which can be understood as a nice guy, you call him, he comes.

Here’s an example:

    var a1 = 1; // Good guy no. 1
    function fn1 () {
        a2 = 2;  // Good guy # 2
        var b1 = 11  // I am not a good person
        function fn2 () {
            window.a3 = 3 // Good guy no. 3
            function fn3 () {
                console.log(b1) / / 11
            }
            fn3()
        }
        fn2()
    }
    fn1()
    fn2()  // fn2 is not defined
    console.log(a1) / / 1
    console.log(a2) / / 2
    console.log(a3) / / 3
    console.log(b1) //b1 is not defined
Copy the code

In this example, A1, A2 and A3 can be accessed globally. Even though A2 and A3 are defined in the function fn1 and fn2 respectively, these are also three ways to define global variables. However, it is recommended that you define global variables as little as possible in the process of programming, because in our projects, especially large projects, We have too many variables to define, and too many global variables can easily cause variable name conflicts and pollute the namespace.

It is not a declaration operation, but an assignment operation. When a2 = 2 is executed, it will try to find A2 in the function fn1. If it fails to find a2, it will continue to search up the scope chain until it reaches the global scope. If the global scope is still not found, an A2 property is created and assigned to 2. It would take too much space to elaborate on the differences, so you can find out for yourself. (I may write a separate article to explain it after I fully understand it)

It is important to note that a variable is not a global variable even if it is defined with a let and const in the global scope.

    var a = 1
    let b = 2
    console.log(window.a,window.b) // 1, undefind
Copy the code

Figure (also cut by myself):

Going back to the previous example, a function scope is declared inside a function and only accessible inside a function, such as B1 and fn2, which are not accessible in the global scope, but inside Fn3 you can find B1, which is looking for a variable from inside the function, In the example of ‘console.log(b1)’, the search process is fn3 -> fn2 -> fn1, which is the scope chain we will talk about later.

2. Block level scope

The concept of block-level scope did not exist before ES6. It was introduced with the addition of let and const in ES6. Let declares a variable, Const, on the other hand, declares an immutable constant. (To get a start, let and var are the only comparisons used in this section.)

Block-level scopes can be created in the following situations:

  • Function of the internal
  • Inside {} is a block-level scope
  • For loop

Let’s start with a simple example to see the difference between var and let:

{
    var a = 1
    let b = 2
}
console.log(a) / / 1
console.log(b) // b is not defined
Copy the code

Var ignores {} and can still be called from the global scope, while the let declaration b is subtly inside {}. Because var has no block-level scope, it does not understand {} at all. The only {} it understands is in function declarations. Var does not have a block-level scope. Var does have a function scope.

function fn () {
    var a = 1
}
console.log(a) // a is not defined
Copy the code

The var is still wrapped in {}.

Let’s talk about the scope of the for loop.

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

Guess what the output is, 0, 1, 2, 3, 4? Naive! Var has no block-level scope. After 500 milliseconds, the for loop will have finished executing, so it will output the same I that finished all the loops. The final output is 5, 5, 5, 5, 5. Let’s look at let:

    for(let i=0; i<5; i++){ setTimeout((a)= > console.log(i),500)}Copy the code

The output of this piece of code is what you think it is, 0, 1, 2, 3, 4, because every time I goes through the loop it’s going to be printed out in a separate block-level scope, and the separate block-level scope is going to be the same I as it was in that loop.

Spoofing scope

This section is for wide viewing and is strongly not recommended (it may cause performance degradation).

As we understand it, the scope is determined when we write the code, so how can there be such a thing as “cheating”? There are two ways to do this in JavaScript:

eval

eval(…) The function takes a string as an argument and executes the string as if it were code written into the program. In other words, eval(…) The accepted argument replaces eval(…). Becomes the code to be executed in the program. Here’s an example:

    function fn (a,str){
        eval(str)
        console.log(a,b)  / / 0 2
    }
    var b = 1
    fn(0.'b=2')
Copy the code

Where the string ‘b=2’ is executed at eval(STR), thus printing “0, 2” instead of “0, 1”.

with

With () is a lazy way to refer to multiple properties of the same object over and over without referring to the object itself. Here’s an example:

var obj = {
    a: 1.b: 2.c: 3,}// multiple references to obj
obj.a = 11
obj.b = 22
obj.c = 33

// use with once
with (obj) {
    a = 111
    b = 222
    c = 333
}
Copy the code

Don’t you think with is too convenient, but! Global variables (a, B, c, and c) with (a, B, and C) are global variables created without var (a, B, and C). Eval and With both degrade the performance of our programs. This is just to broaden our view and not to give detailed examples

3. Scope chain

We have covered scope chains more or less in previous articles, but remember that scope is determined when code is written, regardless of where it is called. This chapter will cover scope chains in more detail.

1. Free variables

What is scope? Simply put, there is no variable defined in the current scope that needs to be looked up. Here’s an example:

var a = 0
function fn1 () {
    console.log(a)  / / 0
}
function fn2 () {
    var a = 1
    function fn3 () {
        fn1() 
    }
    fn3()
}
fn2()
Copy the code

In this case, the output of a in fn1() is the free variable, because there is no variable a in fn1(), and it needs to look out of scope. Some people might say, “It was called in fn3, looked out of scope for fn2, and the output should be 1.” Let’s go back to the sentence at the beginning of this chapter: scope is determined when code is written, regardless of the location of the call. So, even if fn1() is called in Fn3, it looks up in the order: fn1 -> global. So what it looks for is also a in the global scope.

2. LHS and RHS

This section should have been covered in compilation principles, but on second thought it was better to cover it after scopes. First, to understand the concept:

LHS(Left Hand Search) means to assign a value to a variable or write to memory, the target of the operation.

RHS(Right Hand Search) stands for variable lookup or reading from memory, i.e. source of operation.

For those who have not learned this knowledge, it may be difficult to understand. In fact, it is very simple, for example:

var a = 2
Copy the code

In this line of code, we operate on a and assign 2 to it, so a is the target of the operation and 2 is the source of the operation, so we have an LHS reference to A and an RHS reference to 2. Is it very simple, but in our coding process, our operation is often not as simple as “=”, but this does not affect our judgment of LHS and RHS, we can imagine “=” as a function, No matter what the content of the function is (it can even do nothing), we are making an LHS reference to A and an RHS reference to 2.

Implicit reference

Here’s an example:

function foo (a) {
    console.log(a)
}
foo (2)
Copy the code

Foo (2) and console.log(a) have two RHS references, but when was a assigned to 2? The answer is that while passing arguments, the function foo() is called and its parameter a is assigned to 2.

Take two examples from a book

The following excerpt comes from JavaScript you Don’t Know (Volume 1), which explains scopes and this in great detail and is highly recommended to read.

The same chestnut:

function foo (a) {
    console.log(a)
}
foo (2)
Copy the code

From the book:

function foo (a) {
    console.log( a + b )
}
var b = 2
foo (2)
Copy the code

From the book:

4. Execution context

This is arguably the most abstract and difficult part of this article… From its name is very difficult to understand, from our primary school Chinese knowledge, this is clearly a verb phrase, but it is a noun (???) .

So what is an execution context? When the JS engine parses executable code, it does some pre-execution work to predict the JS execution environment. This concept is the Execution Context.

1. Create a context

The creation of an execution context has two phases: the creation phase and the execution phase:

The execution phase is simply the execution of the code, and we’re going to talk in detail about the creation phase.

The create phase takes place before the code is executed. The content created during the create phase is:

  1. This value
  2. Lexical environment components
  3. Variable environment component

1. This value

This is known as the This binding, which depends on the parameters when there is a call, apply, or bind, and otherwise depends on how the function is called, so I won’t go into details here.

2. Lexical environment components

The lexical environment internally contains two components:

  1. Environment logger: The actual location where function and variable declarations are stored.
  2. External environment reference: An accessible parent lexical environment.

Since the global environment is the top-level scope, the global environment’s external environment reference component is NULL, and the environment logger is also divided into two cases:

  1. In the global context: The environment logger is the object environment logger that defines the relationship between variables and functions that appear in the global context.
  2. In a functional environment: The environment logger is a declarative environment logger used to store functions, variables, and parameters.

Simply put, the environment logger stores an additional arguement object and the number of arguements in the functional environment.

3. Variable environment components

Variable environment components are similar to lexical environment components in that they differ:

  • The objects declared by let and const are stored in the lexical environment component
  • The objects declared by var and function are stored in the variable environment component

There is a simple example of this in Section 2.1 of this article.

2. Execute the context stack

I believe that most of you have some basic concept of stack, here is a picture to explain:

var scope = 'global scope';
function first() {
    var scope = 'first EC';
    function second() {
        var scope = 'second EC'
    }
    function third() {
        var scope = 'third EC'
    }
    second()
    third()
}
first()
Copy the code

In this chestnut:

The global context is created and pushed. Create the function first context and push it onto the stack, execute the function first; Create the second context in first and push it onto the stack. Execute the function second. When second completes execution, the second context is removed from the stack. Create a context for function third and push it onto the stack. Execute function third. When third completes, the third context is removed from the stack. When first is executed, the first context is removed from the stack. Global context out of the stack.Copy the code

conclusion

These days write scope of knowledge more write more feel scope of content is really much, found a lot of knowledge blind area, also checked a lot of information, found that either speak too vague, or is extremely esoteric (I too vegetables! , when writing this article subdirectory of the thief vexed, it is difficult to distinguish some small knowledge belongs to which piece of content, also led to the writing of things a little messy, there are insufficient places please also friends pointed out, if to give a help, please also point out a praise ~, very grateful!