The foreword 0.
I recently finished reading the first part of What You Don’t Know about JavsScript, and in case I forget it, I want to share. I’d like to share with you my new knowledge about scopes and closures. I will continue to share other learnings from this series as I read further.
1. What is the scope
One of the most basic features of almost any programming language is the ability to store values in variables and then access and modify those values later. In fact, the ability to store and access the values of variables brings state to the program. Without the concept of state, a program could perform some simple tasks, but it would be highly limited to doing something very interesting. But introducing variables into a program raises several interesting questions, which we’ll discuss: Where do these variables live? In other words, where are they stored? Most importantly, how does the program find them when it needs them? These problems illustrate the need for a well-designed set of rules to store variables and make them easy to find later. This set of rules is called scoping.
A scope is directly defined at the beginning of chapter 1 with this description: a scope is a set of rules for storing and finding variables. Obviously, any programming language has its own scoping rules. These rules are relevant to compiling and running a language. Therefore, to understand the scope of JavaScript, you need to start with how JavaScript is compiled.
1.1 Compilation Principles
There are two broad classes of programming languages: compiled and interpreted. Compiled languages can be compiled to produce an executable file that can then be run without any outside assistance. Interpreted languages are compiled and run at the same time, requiring an interpreter to compile and run at the same time. Our main character today, JS is an interpreted language. As we know, JS was originally run in the browser. Although nodeJS is separated from the browser, the underlying nodeJS uses a V8 engine written in c++. The browser that parses JS is also called the engine. Therefore, we can see that the tools that parse JavaScript are called engines. One of the interesting things about the book is that it personifies the process of compilation. It treats the engine as a big brother, with two smaller brothers below, called the compiler and scope. Why do you say that? Because the engine runs through the whole compilation and running process, it is the backbone that leads the compilation and running of the program, so it is the big brother; The compiler is mainly responsible for the compilation process, which is to translate the program into the machine can understand the content; The scope is responsible for controlling the storage and lookup permissions of variables. After introducing the three protagonists of JS compilation, next we introduce the compilation process of JS. The compilation process of JS is divided into three steps:
- Word segmentation/lexical analysis: The decomposition of a program into lexical units
- Parsing/parsing: The final result is to generate an abstract syntax tree (AST).
- Code generation: The process of converting an AST into executable code
The compilation process of JS is performed in the compiler. The complete compilation process of JS is shown in the figure below. First, through lexical analysis, THE JS code will be transformed into meaningful code blocks for the program, called lexical units; Next, these lexical units are parsed into abstract syntax trees; Finally, the abstract syntax tree is turned into executable code.
1.2 Understanding scopes
Next, we understand scopes in four ways: how variables are queried, the role that scopes play, the chain of scopes, and error reporting.
1.2.1 Two Query Methods
During compilation, querying variables is initiated by the compiler. The object being queried is a scope. In simple terms, the compiler queries a variable into the scope. While running, query variables are initiated by the engine. Variables can be queried in two ways: LHS query and RHS query. Represents the left and right sides of the query assignment operation. What about left and right here? To understand left and right, you first need to understand the assignment process. The assignment operation requires two elements: the container to hold the variable, and the value to be added to the container. An assignment operation adds a value to a variable container. For example: var a = 2; In this line of code, a is the container that holds the variable, and 2 is the value in the container. This code is putting the 2 on the right into the A on the left. I think that’s where you get the meaning of left and right. RHS looks for the value of the variable, and LHS looks for the storage space of the variable. Finally, let’s put a full stop to this with a simple example: Find the LHS and RHS in the following code
function foo(a) {
var b = a;
return a + b;
}
var c = foo( 2 );
Copy the code
Conclusion:
- LHS (three) :
var c
The variables in thec
foo(a)
In the form of parametersa
var b
The variables in theb
- RHS (around) :
c=foo()
In thefoo()
foo(2)
In the2
return
Both variables of are RHS
1.2.2 Roles played by scopes
The scope acts as a variable manager, both during compilation and during runtime, and can never leave the scope whenever a variable is used.
1.2.3 Scope chain
Action on nesting occurs when a block or function is nested within another block or function. Therefore, the rule of variable lookup is that if a variable cannot be found in the current scope, the engine will continue searching in the outer nested scope until it finds the variable, or stop at the outermost scope (global scope). A scope chain is the space in which a variable is nested in blocks or functions to hold related scopes. Here, I use a picture from the book to illustrate:
We compare the scope chain to a tall building, where the first floor represents the current execution scope; The top level represents the global scope. When we need to find a variable (LHS and RHS are the same), we need to find it at the current floor; If you can’t find them, go upstairs and look for them; Until you reach the top floor. Return if the variable is found during this process, raise an exception otherwise.
1.2.4 Two Types of errors
- ReferenceError: Related to scope validation failure
- If the RHS query cannot find the required variable in all nested scopes, the engine will raise a RefrenceError exception
- When the engine executes an LHS query, in non-strict mode, if the target variable cannot be found in the global scope, a variable with that name is created in the global scope
- In strict mode, when the engine executes an LHS query, it throws a RefrenceError exception if the target variable cannot be found
- TypeError: the representative is used to verify that the result was successful, but that the operation on the result was illegal or unreasonable
2. Lexical scope
There are two main working models for scopes:
- Lexical scope: This model is common and is used by most programming languages
- Dynamic scope: A few programming languages are in use, such as Bash scripts, Perl, and so on
In this section we introduce lexical scope. Lexical scope is the scope defined at this stage. In other words, lexical scope is determined by where variables and blocks are scoped when code is written. Here’s an example:
function foo(a){
var b = a * 2
function bar(c) {
console.log(a,b,c)
}
bar(b*3)
}
foo(2)
Copy the code
There are three hierarchically nested scopes in this code:
- The outermost layer is the global scope and contains a function: foo
- The middle layer is the scope created by foo, containing variables a and b and a function bar
- The innermost layer is the scope created by bar and contains the variable c
These scopes are already in place when the code is defined. That is, a.js file is created with its global scope defined; When a function is defined, its scope is created; Some code blocks are also created in their own scope. Each scope contains all identifiers, or variable names. The next topic on lexical scope is masking effects. The scope lookup stops when the first matching identifier is found. This results in multiple nested scopes that only match the values of variables that are relatively early in the scope chain. This is the shadowing effect, where the internal identifier masks the external identifier. Here’s an example:
var a = 3;
function foo(){
var a = 2;
console.log(a)
}
foo()
Copy the code
In this code, the output is 2. Obviously, this code defines two a variables. The variable a=2 in function foo() is first found. At this point, the search stops and the results are printed. The variable A, defined in the global scope, is obscured. At the end of the book, lexical scopes introduce two ways to trick lexical scopes:
- eval
- with
However, this writing method is not recommended because it is too performance intensive. I won’t go into much detail here.