Lexical Environment
Official definition
official ES2020 defines the Lexical Environment (Lexical Environment) as follows:
A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.
Lexical environment is a specification type, which is based on the lexical nesting structure of ECMAScript code to define the relationship between identifiers and specific variables and functions. The lexical environment consists of an environment record (environment record) and an external lexical environment that may be null.
It’s very detailed, but it’s hard to understand 🤔
Below, we use a compiling process of JS in V8 to explain more intuitively.
The compilation process of JS in V8 is explained more intuitively
It is roughly divided into three steps:
- first step of lexical analysis : When V8 just gets the execution context, it will perform Tokenizing/Lexing analysis (Tokenizing/Lexing) line by line from top to bottom, for example,
var a = 1;
, which will be divided intovar
,a
,1
, 0607e6debd27;
such an atomic symbol ((atomic token). Lexical analysis = refers to the registered variable declaration + function declaration + formal parameters of the function declaration. - second step of grammatical analysis : After the lexical analysis is over, the grammatical analysis will be done, and the engine will parse the token into an abstract syntax tree (AST). In this step, it will detect whether there are grammatical errors, and if there are any, the error will be reported directly. Execute
var a = 1;
console.log(a);
a = ;
// Uncaught SyntaxError: Unexpected token ;
// 代码并没有打印出来 1 ,而是直接报错,说明在代码执行前进行了词法分析、语法分析
- Note: lexical analysis and grammatical analysis are not completely independent, but run in a staggered manner. In other words, it is not necessary to wait for all tokens to be generated before using the parser to process them. Generally, every time a token is obtained, a parser is used to process it.
- third step of code generation : The last step is to convert the AST into a machine instruction code that the computer can recognize
In the first step, we saw that there is lexical analysis, which is used to register variable declarations, function declarations, and function declaration formal parameters. When the subsequent code is executed, you can know where to get variable values and functions. This place of registration is the lexical environment.
The lexical environment consists of two parts:
- environment record : the actual location of storage variables and function declarations, the place where the variables are actually registered
- reference to the external environment : means that it can access its external lexical environment, which is the key to linking the scope chain
The set of identifiers that can be accessed by each environment is called the "scope". We nested the scope layer by layer to form a "scope chain".
There are two lexical environments:
- global environment : is a lexical environment without an external environment, and its external environment reference is null . With a global object (window object) and its associated methods and properties (such as array methods) and any user-defined global variables,
this
points to this global object. - function environment : The variables defined by the user in the function are stored in the environment record , which contains the
arguments
object. The reference to the external environment can be the global environment or the external function environment that contains internal functions.
environmental record also has two types:
- Declarative Environment Record : Store variables, functions, and parameters. A function environment contains declarative environment records.
- object environment record : used to define the association of variables and functions that appear in the global execution context. The global environment contains object environment records.
If expressed in the form of pseudo code, the lexical environment is like this:
GlobalExectionContext = { // 全局执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object", // 全局环境
// ...
// 标识符绑定在这里
},
outer: <null> // 对外部环境的引用
}
}
FunctionExectionContext = { // 函数执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Declarative", // 函数环境
// ...
// 标识符绑定在这里 // 对外部环境的引用
},
outer: <Global or outer function environment reference>
}
}
E.g:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
Corresponding execution context and lexical environment:
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
c: undefined,
}
outer: <null>
}
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
The lexical environment corresponds to the structure of the code written by ourselves, that is, what our own code looks like, and the lexical environment is what it looks like. The lexical environment is determined when the code is defined, and has nothing to do with where the code is called. So JS uses lexical scope (static scope), that is, its scope is statically determined after the code is written.
Static scope vs dynamic scope
The dynamic scope is based on the stack structure. Local variables and function parameters are stored in the stack. Therefore, the value of the variable is determined by the execution context of the current stack when the code is running. The static scope means that the value of the variable is determined when it is created, and the location of the source code determines the value of the variable.
var x = 1;
function foo() {
var y = x + 1;
return y;
}
function bar() {
var x = 2;
return foo();
}
foo(); // 静态作用域: 2; 动态作用域: 2
bar(); // 静态作用域: 2; 动态作用域: 3
In this case, the implementation of structural static scope and dynamic scope may be inconsistent, bar
essentially perform foo
function, if it is static scope, then, bar
function variables x
in foo
to determine when the function is created That is to say, the variable x
has always been 1
, and both outputs should be 2
. The dynamic scope returns different results x
Therefore, dynamic scope often brings uncertainty, and it cannot determine which scope the value of a variable comes from.
Most current programming languages use static scope rules, such as C/C++, C#, Python, Java, JavaScript, etc. Languages that use dynamic scope include Emacs Lisp, Common Lisp (with static scope), Perl ( Both have static scope). The names used in C/C++ macros are also in dynamic scope.
Lexical environment and closures
A function is bundled with a reference to its surrounding state ( lexical environment, ) (or the function is surrounded by references), this combination is closure ( closure )——MDN
In other words, the closure is composed of the function lexical environment that declares the function.
var x = 1;
function foo() {
var y = 2; // 自由变量
function bar() {
var z = 3; //自由变量
return x + y + z;
}
return bar;
}
var test = foo();
test(); // 6
Based on our understanding of the lexical environment, the above example can be abstracted into the following pseudo code:
GlobalEnvironment = {
EnvironmentRecord: {
// 内置标识符
Array: '<func>',
Object: '<func>',
// 等等..
// 自定义标识符
x: 1
},
outer: null
};
fooEnvironment = {
EnvironmentRecord: {
y: 2,
bar: '<func>'
}
outer: GlobalEnvironment
};
barEnvironment = {
EnvironmentRecord: {
z: 3
}
outer: fooEnvironment
};
As mentioned earlier, the lexical scope is also called the static scope, and the variable is determined at the lexical stage, that is, when it is defined. Although it is called bar
foo
is a closure function, even if it is executed outside the lexical scope defined by itself, it has always maintained its own scope. The so-called closure function, that is, this function encloses its own definition of the environment, forming a closure, so foo
will not bar
, which is the characteristic of static scope.
In order to implement closures, we cannot use a dynamic stack with dynamic scope to store variables. If so, when the function returns, the variable must be popped out of the stack and no longer exists, which is inconsistent with the original closure definition. In fact, the closure data of the external environment is stored in the "heap", so that the internal variables still exist even after the function returns (even if its execution context has been popped from the stack).
At last
This article was first published from "Three minutes to learn the front-end", three minutes a day, advanced a small front-end tip
Interview Question
Algorithm question bank
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。