Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”. This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
In JS, each variable is stateful, such as global or local, declared or undeclared. If the variable has no state, it may be possible to perform simple operations, but complex operations are limited.
Where are variables stored? How do I find it when the program needs it?
Therefore, it is necessary to design a good set of rules to store variables, so that it is easier to find variables in the future.
This set of rules is called scope.
Compilation principle
JavaScript is often categorized as a “dynamic” or “interpreted execution” language, but it is actually a compiled language.
Unlike traditional compiled languages, JavaScript is not compiled ahead of time. Most of the time compilation takes place within a few microseconds (or less) before the code executes.
The process of compiling a traditional language
In a traditional compilation language, a piece of source code in a program goes through three steps, collectively called compilation, before it is executed
- Word Segmentation/Lexical Analysis (Tokenizing/Lexing)
- This process breaks down a string of characters into blocks of code that make sense (to the programming language), called lexical units (tokens).
- Parsing/Parsing
- This process converts a stream of lexical units (arrays) into a hierarchical nested tree of elements that represents the syntactic structure of the program. This tree is called an Abstract SyntaxTree (AST).
- Code generation
- The process of turning an AST into executable code is called code generation. This process depends on language, target platform, and so on.
JS compilation principle
JavaScript engines are much more complex than compilers for languages that compile in three steps. For example, there are specific steps in the parsing and code generation phases to optimize runtime performance, including for redundant elements.
🎈 for example:
var name = 'wuyanfeiying';
- Word segmentation/lexical analysis
Splits the entire code string into an array of minimal syntax units, each element being a minimal syntax unit. Tokens
[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "name"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "String",
"value": "'wuyanfeiying'"
}
]
Copy the code
- Parsing/parsing
On the basis of word segmentation, the relationship between analysis grammar units is established and the results of word segmentation are formed into a tree structure SyntaxTree according to the relationship between each other
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "name"
},
"init": {
"type": "Literal",
"value": "wuyanfeiying",
"raw": "'wuyanfeiying'"
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}
Copy the code
- Code generation
The process of turning an AST into executable code is called code generation.
Var name = ‘wuyanfeiying’; The AST is converted to a set of machine instructions that create a variable called name (including allocating memory, etc.) and store a value in name.
Understanding scope
To understand a few concepts:
engine
Responsible for the compilation and execution of the entire JavaScript program.
The compiler
Responsible for grammar analysis and code generation.
scope
Responsible for collecting and maintaining a series of queries made up of all declared identifiers (variables) and enforcing a very strict set of rules that determine access to these identifiers by currently executing code.
🎈 var name = ‘wuyanfeiying’; , will enter the following two stages:
- Stage 1, the protagonist
The compiler
:- inCompile time, looks for variables with that name in the collection of the same scope.
- Yes, the compiler ignores this declaration and continues compiling.
- No, the current scope will declare a new name
name
The variables.
- inCompile time, looks for variables with that name in the collection of the same scope.
Next, the compiler generates run-time code for the engine to handle var name = ‘wuyanfeiying’; This assignment operation.
- Stage 2, the protagonist
engine
:- The engine is running, first looks in the current scope collection to see if there is a called
name
The variables.- If so, the engine uses this variable;
- If not, the engine continues to look for the variable.
- The engine is running, first looks in the current scope collection to see if there is a called
If the engine finally finds the name variable, it assigns ‘wuyanfeiying’ to it. Otherwise the engine will throw an exception!
LHS and RHS
Var name = ‘wuyanfeiying’; When the engine executes it, it looks for the variable name to determine whether it has been declared. The lookup process is assisted by the scope, but how the engine performs the lookup affects the final result.
The terms LHS and RHS are used when the engine queries variables.
There are two types of query:
- LHS(Left-hand Side): Who is the target of the assignment operation
- RHS(Right-hand Side): who is the source of the assignment operation
🌰 in JavaScript You Don’t Know (Volume 1)
Look for all LHS and RHS:
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
Copy the code
🎈 Own understanding:
If the purpose of the lookup is to assign a value to a variable, an LHS query is used; If the goal is to get the value of a variable, RHS queries are used.
The scope chain
As described earlier, a scope is a set of rules for finding variables by name.
What does the engine do if it doesn’t find a variable in the current scope?
The engine then continues searching in the outer nested scope until it finds the variable or reaches the outermost scope. The scope chain is now formed.
The search method here is the same as before: LHS and RHS queries.
Both LHS and RHS queries start in the current execution scope, and if needed (that is, they do not find the desired identifier), they continue to look for the target identifier in the upper scope, so that each step up to the global scope is stopped with or without finding it.
Exception handling
- The engine performs LHS query
- 1.1 In non-strict mode, if not found in the current scope, the search continues one level up, and if not found in the final global scope. A variable with that name is created.
- Under 1.2 Strict mode, as with RHS, exceptions are thrown
ReferenceErroe
The exception. becauseStrict mode disallows automatic or implicit creation of global variables 。
- The engine performs RHS query
- 2.1 If it cannot be found, it will also follow the scope net to find. If it cannot be found, an exception will be thrown
ReferenceErroe
The exception. - 2.2 If it is found, but you apply a”Unreasonable operation“, will also report an error
TypeError
.
In the effort to read and learn, refer to the following, look up to the big guys ~ 🙆♂️
reference
- Parsing the JS compilation process
- Abstract syntax tree AST
- JavaScript you Don’t Know (Volume 1)
- esprima/parser