xDirectory [1] compilation [2] execution [3] query [4] nesting [5] exception [6] in front of the principle
JavaScript has a well-designed set of rules to store variables, which can then be easily found, a set of rules called scopes. The scope seems simple, but complex, because the scope and this mechanism is very easy to confuse, make understanding the principle of scope is more important. This article is the first in-depth understanding of the JavaScript Scope series-Internal principles
The internal principles are divided into five parts: compilation, execution, query, nesting and exception, and the principle is explained by an example process.
Compile
Take var a = 2; For example, the internal compilation process of JavaScript, mainly includes the following three steps:
"1" participle (tokenizing)
Break up a string of characters into meaningful blocks of code called Lexical units (tokens)
var a = 2; is decomposed into the following lexical units: var, a, =, 2,; These lexical units form an array of lexical cell streams
"2" parsing (parsing)
The lexical unit stream array is transformed into a tree representing the syntactic structure of the program, which is called "Abstract Syntax Tree" (AST), which is composed of nested elements.
var a = 2; there is a top-level node called variabledeclaration in the abstract syntax tree, followed by a child node called identifier (whose value is a), and a child node called Assignmentexpression. And the node has a child node called numericliteral (whose value is 2)
"3" code generation
The process of converting an AST to executable code is called code generation
var a=2; The abstract syntax tree is converted to a set of machine instructions to create a variable called a (including allocating memory, etc.) and store the value 2 in a
In fact, the JavaScript engine's compilation process is much more complex, including a lot of optimizations, and the three steps above are a basic overview of the compilation process
Any code fragment is compiled before execution, and in most cases the compilation occurs in microseconds before the code executes. The JavaScript compiler first a=2 the Var, the program compiles, then prepares to execute it, and usually executes it immediately.
Perform
In short, the compilation process is the compiler to decompose the program into lexical units (tokens), and then parse the lexical unit into a syntax tree (AST), and then turn the syntax tree into a machine instruction waiting for execution of the process
In fact, the code is compiled and executed. The following is still a var a = 2; for example, an in-depth explanation of the compilation and execution process
"1" compilation
1. The compiler looks for scopes that already have a variable called a that exists in the same scope's collection. 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 and name a
2. The compiler will var a = 2; This code fragment is compiled into a machine instruction for execution
[note] According to compiler compiler principle, the duplicate declaration in JavaScript is legal
// test appears for the first time in the scope, so declare a new variable and assign 20 to test var test =; // test already exists in scope and is used directly to replace the 20 assignment with the value of var test = 30;
"2" execution
1. When the engine runs, the scope is queried first, and there is a variable called a in the current scope collection. If so, the engine will use this variable, and if no, the engine will continue to look for the variable
2. If the engine finally finds the variable A, it will assign the value of 2 to it. Otherwise the engine throws an exception
Inquire
In the first step of the engine execution, the variable A is queried, and the query is called a LHS query. In fact, engine queries are divided into two types: LHS queries and RHS queries
To understand the literal meaning, when the variable appears to the left of the assignment Operation LHS Query, appear on the right when the RHS query
More precisely, the RHS query is no different from simply finding the value of a variable, while the LHS query is an attempt to find the container itself of the variable so that it can be assigned a value
function Foo (a) { console.log (a); // 2 2);
In this code, a total of 4 queries are included:
1. Foo (...) RHS a reference to Foo
2. function parameter A = 2 a LHS reference to a
3, Console.log (...) A RHS reference to the console object and check if it has a log method
4, Console.log (a) a RHS reference to a, and the resulting value passed to the Console.log (...)
Nesting
When a variable cannot be found in the current scope, the engine continues to look in the outer nested scope until the variable is found, or the outermost scope (that is, the global scope) is reached
function Foo (a) { + b);} var b = 2; foo (2); // 4
In the code snippet, the scope foo () function is nested in the global scope. The engine first looks for the variable B in the scope of the Foo () function and attempts to RHS it, not found; Then, the engine looks for B in the global scope, finds it successfully, RHS a reference to it, and assigns a value of 2 to B
Abnormal
Why is it important to differentiate between LHS and RHS? Because the variables are not declared (variables cannot be found in any scope), the behavior of the two queries is different
RHS
"1" If the RHS query fails, the engine throws an Referenceerror (reference error) exception
// the variable cannot be found when a RHS query is made on B. In other words, this is an "undeclared" variable function foo (a) { = b; } Foo (); // referenceerror:b is not defined
"2" If the RHS query finds a variable, but attempts to unreasonably manipulate the value of a variable, such as a function call to a non-function type value, or reference a property in null or undefined, the engine throws 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 a 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 () { = 1; } Foo (); Console.log (a); // 1
"2" If the LHS query fails in strict mode and does not create and return a global variable, the engine throws a Referenceerror exception similar to the RHS query failure
function foo () { ' use strict '; = 1; } Foo (); Console.log (a); // referenceerror:a is not defined
Principle
function Foo (a) { console.log (a);} Foo (2);
The code snippet above illustrates the inner workings of the scope, divided into the following steps:
The "1" engine needs to be foo (...) The RHS function makes a reference to find Foo in the global scope. Successfully found and executed
The "2" engine needs to pass the parameter a=2 of the Foo function, lhs a reference to a, and find a in the Foo function scope. Successfully found and assigned 2 to a
The "3" engine needs to perform a console.log (...) to make a RHS reference to the console object and find the console object in the Foo function scope. Because the console is a built-in object, it is successfully found
The "4" engine looks for the log (...) in the console object. method to successfully find
The "5" engine needs to execute Console.log (a), RHS a reference to a, find a in the scope of the Foo function, and successfully locate and execute
"6" Then, the engine to the value of a, that is, 2 to Console.log (...) In
"7" Finally, console output 2
In-depth understanding of the JavaScript Scope series first-internal principles