In-depth understanding of javascript scope system part 1 - internal principles

previous words

JavaScript has a well-designed set of rules for storing variables and finding them easily later, this set of rules is called scoping. Scope looks simple, but it is complicated. Because scope and scope are this机制very easy to confuse, it is more important to understand the principle of scope.

Internal principles are divided into: compilation, execution, query, nesting and exception

1. Compile

Take var a = 2;an example to illustrate the process of Javascript internal compilation, which is mainly divided into three steps.

[1]: Tokenizing: Decompose a string of characters into meaningful code blocks, which are called tokens.
var a = 2; is decomposed into the following lexical units: var, a, =, 2, ;. These tokens form an array of token streams.

[
    {
        "type": "Keyword",
        "value": "var"
    },
    {
        "type": "Identifier",
        "value": "a"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "Numeric",
        "value": "2"
    },
    {
        "type": "Punctuator",
        "value": ";"
    }
]

[2]: Parsing: The assembly of the lexical unit stream is replaced by a tree representing the program syntax structure composed of elements nested one level at a time. This tree is called "Abstract Syntax Tree" (AST).

{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "a"
                    },
                    "init": {
                        "type": "Literal",
                        "value": 2,
                        "raw": "2"
                    }
                }
            ],
            "kind": "var"
        }
    ],
    "sourceType": "script"
}

write picture description here

AST (Abstract Syntax Tree): refers to the tree structure corresponding to the source code syntax, that is, the source code of a programming language maps the statements in the source code to each node in the tree by building a syntax tree superior. The program itself can be mapped into a syntax tree, and by manipulating the syntax tree, we can accurately obtain every precise node in the program code. Such as declaration statement, assignment statement. This process will convert the js code to JSON format

[3]: Code generation: The process of converting AST into executable code is called code generation.
The abstract syntax tree of var a = 2; is turned into a set of machine instructions to create a variable called a (including memory allocation, etc.) and store the value 2 in a. (What is stored here is a simple type of data, so the address of this memory is bound to the variable name a, and the value 2 is stored in the address space).
In fact, the compilation process of the javascript engine is much more complicated, including a large number of optimization operations. The above three steps are the basic overview of the compilation process.
Any code fragment must be compiled before execution. In most cases, the compilation happens a few subtleties before the code is executed. The javascript compiler will first compile the var a = 2; program, and then prepare to execute it, and usually executes it right away.

2. Implementation

In short, the compilation process is the process in which the compiler decomposes the program into lexical units (tokens), then parses the lexical units into syntax trees (AST), and turns the syntax trees into machine instructions waiting to be executed.

In fact: the code does compile and execute. Below is an in-depth explanation.
[1]:
Compile The compiler checks whether a variable named a already exists in the same scope set in the scope. If so, the compiler ignores the declaration and continues to compile. Otherwise, it asks the scope to declare a new variable in the current scope's collection, named a.

var a = 1;
  //编译时,首先会查找作用域是否已经有一个名称为a的变量存在于同一个作用域的集合中。
   // 如果是,编译器会忽略该声明,继续进行编译,否则,它会要求作用域在当前作用域集合中声明一个新的变量,
   // 并命名为a
  var a;
  console.log(a); //1;

The compiler compiles var a = 2; this code fragment into machine instructions for execution.
[Notes]: According to the compilation principle of the compiler, repeated declarations in javascript are legal.

//test在作用域中首次出现,所以声明新变量,并将20赋值给test
var test = 20;
//test在作用域中已经存在,直接使用,将20的赋值替换成30
var test = 30;

[2]: Execute
1. When the engine is running, it will first query the scope to see if there is a variable called a in the current scope set. If it is, the engine will use the variable, if not, the engine will continue looking up the variable.
2. If the engine finally finds the variable a, it will assign 2 to it. Otherwise the engine will throw an exception.

3. Inquiry

In the first operation performed by the engine, the variable a is queried (queries whether there is such a variable a), which is called LHS query (find this variable container). Actually, there are two types of engine queries: LHS queries and RHS queries.
From the literal meaning, when the variable appears on the left side of the assignment operation, the LHS query is performed, and when the variable appears on the right side, the RHS query is performed.
More precisely, RHS queries are no different from simply finding the value of a variable, whereas LHS queries are trying to find the variable's container itself, so that it can be assigned a value

function foo(a){
    console.log(a);//2
}
foo( 2 );

In this code, there are a total of 4 queries, which are:
foo(…) makes an RHS reference to foo (the function call belongs to the RHS query, because the function has been declared, and it is only called now) The
function is passed a = 2 to a The LHS query is performed (the function parameter - the actual parameter is passed to the formal parameter, which is equivalent to finding the variable container itself and assigning it a value)
console.log(...) makes an RHS reference to the console object and checks whether it has a log The method
console.log(a) makes an RHS reference to a and passes the resulting value to console.log(…)

4. Nesting

When a variable cannot be found in the current scope, the engine will search in the outer nested scope until the variable is found or the global scope is reached.

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

In this snippet, the scoped foo() function is nested in the global scope, and the engine first looks for the variable b in the foo() function's scope and tries to make an RHS reference to it. If it is not found, then the engine will look for b in the global scope. After successfully finding it, it will make an RHS reference to it and find that its value is 2.

5. Abnormal

Why is it important to distinguish between LHS and RHS? Because the two queries do not behave the same in the case where the variable has not been declared (the variable cannot be found in any scope).
RHS
[1] If the RHS query fails, the engine will throw a ReferenceError (reference error) exception.

//对b进行RHS查询时,无法找到该变量。也就是说,这是一个“未声明”的变量
function foo(a){
    a = b;  
}
foo();//ReferenceError: b is not defined

[2] If the RHS query finds a variable, but tries to perform unreasonable operations on the value of the variable, such as calling a function on a non-function type value, or referencing a property in null or undefined, the engine will throw another type Exception: TypeError (type error) exception.

function foo(){
    var b = 0;
    b();
}
foo();//TypeError: b is not a function

LHS
[1] When the engine executes an LHS query, if the variable cannot be found, the global scope creates a variable with that name and returns it to the engine.

function foo(){
    a = 1;  
}
foo();
console.log(a);//1

[2] If the LHS query fails in strict mode, a global variable will not be created and returned, and the engine will throw a ReferenceError exception similar to when the RHS query fails.

function foo(){
    'use strict';
    a = 1;  
}
foo();
console.log(a);//ReferenceError: a is not defined

6. A case is used to illustrate the internal principle.

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

Mainly divided into several steps:

  1. The engine needs an RHS reference for the foo(...) function, looking for foo in the global scope. found and executed successfully.
  2. The engine needs to pass the parameter a = 2 of the foo function; make an LHS reference to a, search for a in the scope of the foo function, and assign it to 2 after successfully finding it.
  3. It causes the need to execute console.log(...), make an RHS reference for the console object, and find the console object in the scope of the foo function. Since the console is a built-in object, it is successfully found.
  4. The engine looks for the log(...) method in the console object and finds it successfully.
  5. The engine needs to execute console, log(a), perform RHS query on a, find a in the scope of the foo() function, find and execute it successfully.
  6. Therefore, the engine passes the value of a, which is 2, to the console, log(…).
  7. Finally, 2 is output in the console.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325663824&siteId=291194637