This is the third day of my participation in the August More text Challenge. For details, see: August More Text Challenge

What is scope

Compilation principle

A piece of source code in a program goes through three steps before execution, collectively known as “compiling.”

  • Tokenizing/Lexing

This process breaks up a string of characters into meaningful blocks of code called lexical units. For example, consider the program var a = 2; This program is usually broken down into lexical units such as var, a, =, 2,; .

  • Parsing/Parsing

The process is to transform a stream of lexical units (array) into a tree of progressively nested elements that represent the syntax structure of the program. This Tree is called an Abstract Syntax Tree (AST).

var a = 2; There might be a top-level node called VariableDeclaration, followed by a child node called Identifier (whose value is a), and a child node called AssignmentExpression. The AssignmentExpression node has a child node called NumericLiteral (which has a value of 2).

  • Code generation

The process of converting the AST into executable code is called code generation. The simple answer is that there is some way to set var a = 2; The AST is converted to a set of machine instructions that create a variable called a (including allocating memory, etc.) and store a value in a.

JavaScript engines are much more complex than compilers for languages that have only three steps in the compile phase. For example, the parsing and code generation phases have specific steps to optimize performance, including optimizing for redundant elements. The JavaScript engine has used various methods (such as JIT, which can delay compilation or even implement recompilation) to ensure optimal performance.

Understanding scope

To understand scope, first understand how the program handles it.

  • The engine is responsible for the compilation and execution of the entire JavaScript program from beginning to end.
  • Compiler, responsible for parsing and code generation.
  • Scope, which collects and maintains a set of queries made up of all declared identifiers (variables) and enforces a very strict set of rules that determine the currently executing code’s access to these identifiers.

We treat the interaction of appeals as a dialogue.

dialogue

For var a = 2; In this program, the engine considers that there are two different declarations, one processed by the compiler at compile time and the other by the engine at run time.

The compiler first decompresses the program into lexical units, and then resolves the lexical units into a number structure. When the compiler starts code generation, the compiler does the following:

  1. encountervar a, the compiler asks the scope if a variable of that name already exists in the collection of the same scope. If so, the compiler ignores the declaration and continues compiling. Otherwise it will require the scope to declare a new variable in the current scope’s collection, named a.
  2. The compiler then generates the runtime code for the engine, which is used for processinga = 2The assignment operation. The engine first asks the scope if there is a variable called a in the current set of scopes. If so, the engine will use this variable; If not, the engine continues to look for the variable.

If the engine finds the variable A, it will assign, otherwise it will throw an exception.

Summary: Assignment to a variable performs two actions. First, the compiler declares a variable in the current scope (if it hasn’t been declared before), and then at run time the engine looks for the variable in the scope and assigns it if it can be found.

The compiler said

The compiler generates code during compilation, and when the engine executes it, it looks for the variable A to see if it has been declared. The search process is assisted by scope, but what the engine does affects the final search result.

In this example, the engine performs an LHS query for the variable A, with another query type RHS.

For these two query types, we can understand “who is the target of the assignment operation (LHS)” and “Who is the source of the assignment operation (RHS)”.

Dialogue between engine and scope

The engine and scope process the code in order, including LHS references and RHS references.

Scope nesting

Scope is a set of rules for finding variables by name.

Nesting of scope occurs when a block or function is nested within another block or function. Therefore, if a variable cannot be found in the current scope, the engine will continue to search in the outer nested scope until it is found or reaches the outermost (i.e., global) scope.

The rules for traversing a chain of nested scopes are simple: the engine looks for variables starting at the current execution scope, and if it cannot find them, it continues the search up a level. When the outermost global scope is reached, the search stops whether it is found or not. (LHS and RHS are both searched in the current scope, if not found, go up one level…)

summary

Scope is a set of rules that determine where and how to find variables (identifiers). If the purpose of the lookup is to assign a value to a variable, then the LHS query is used; If the goal is to get the value of the variable, the RHS query is used. The assignment operator causes an LHS query. The = operator or any operation passed as an argument when the function is called will result in an assignment to the associated scope.

The JavaScript engine first compiles the code before it is executed. During this process, declarations like var a = 2 are broken up into two separate steps:

  1. First, var a declares new variables in its scope. This happens at the very beginning, before the code is executed.
  2. Next, a = 2 queries (LHS queries) the variable a and assigns it.

Both LHS and RHS queries start at the current scope, and if needed (that is, they do not find the desired identifier), they continue to search for the target identifier in the upper scope, ascending each level until they reach the global scope, where they stop whether found or not.

An unsuccessful RHS reference causes a ReferenceError exception to be thrown. An unsuccessful LHS reference causes a global variable to be automatically and implicitly created (in non-strict mode) that uses the target of the LHS reference as an identifier, or a ReferenceError exception to be thrown (in strict mode).