Skip to content

Commit c6f85d4

Browse files
committed
update
1 parent 89224c9 commit c6f85d4

7 files changed

Lines changed: 1134 additions & 0 deletions

File tree

‎docs/.vuepress/config.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ module.exports = {
116116
["JavaScript/scope", "说说你对作用域链的理解"],
117117
["JavaScript/prototype", "JavaScript原型,原型链 ? 有什么特点?"],
118118
["JavaScript/inherit", "Javascript如何实现继承?"],
119+
["JavaScript/this", "谈谈this对象的理解"],
120+
["JavaScript/context_stack", "JavaScript中执行上下文和执行栈是什么?"],
121+
["JavaScript/event_Model", "说说JavaScript中的事件模型"],
122+
["JavaScript/typeof_instanceof", "typeof 与 instanceof 区别"],
123+
["JavaScript/event_agent", "解释下什么是事件代理?应用场景?"],
119124
],
120125
}
121126
],

‎docs/JavaScript/context_stack.md‎

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# 面试官:JavaScript中执行上下文和执行栈是什么?
2+
3+
![](https://static.vue-js.com/8652b710-74c1-11eb-85f6-6fac77c0c9b3.png)
4+
5+
6+
## 一、执行上下文
7+
8+
简单的来说,执行上下文是一种对`Javascript`代码执行环境的抽象概念,也就是说只要有`Javascript`代码运行,那么它就一定是运行在执行上下文中
9+
10+
执行上下文的类型分为三种:
11+
12+
- 全局执行上下文:只有一个,浏览器中的全局对象就是 `window `对象,`this` 指向这个全局对象
13+
- 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
14+
- Eval 函数执行上下文: 指的是运行在 `eval` 函数中的代码,很少用而且不建议使用
15+
16+
下面给出全局上下文和函数上下文的例子:
17+
18+
![](https://static.vue-js.com/90dd3b60-74c1-11eb-85f6-6fac77c0c9b3.png)
19+
20+
紫色框住的部分为全局上下文,蓝色和橘色框起来的是不同的函数上下文。只有全局上下文(的变量)能被其他任何上下文访问
21+
22+
可以有任意多个函数上下文,每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问
23+
24+
25+
26+
## 二、生命周期
27+
28+
执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段
29+
30+
### 创建阶段
31+
32+
创建阶段即当函数被调用,但未执行任何其内部代码之前
33+
34+
创建阶段做了三件事:
35+
36+
- 确定 this 的值,也被称为 `This Binding`
37+
- LexicalEnvironment(词法环境) 组件被创建
38+
- VariableEnvironment(变量环境) 组件被创建
39+
40+
伪代码如下:
41+
42+
```js
43+
ExecutionContext = {
44+
ThisBinding = <this value>, // 确定this
45+
LexicalEnvironment = { ... }, // 词法环境
46+
VariableEnvironment = { ... }, // 变量环境
47+
}
48+
```
49+
50+
51+
52+
#### This Binding
53+
54+
确定`this`的值我们前面讲到,`this`的值是在执行的时候才能确认,定义的时候不能确认
55+
56+
57+
#### 词法环境
58+
59+
词法环境有两个组成部分:
60+
61+
- 全局环境:是一个没有外部环境的词法环境,其外部环境引用为` null`,有一个全局对象,`this` 的值指向这个全局对象
62+
63+
- 函数环境:用户在函数中定义的变量被存储在环境记录中,包含了`arguments` 对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境
64+
65+
伪代码如下:
66+
67+
```js
68+
GlobalExectionContext = { // 全局执行上下文
69+
LexicalEnvironment: { // 词法环境
70+
EnvironmentRecord: { // 环境记录
71+
Type: "Object", // 全局环境
72+
// 标识符绑定在这里
73+
outer: <null> // 对外部环境的引用
74+
}
75+
}
76+
77+
FunctionExectionContext = { // 函数执行上下文
78+
LexicalEnvironment: { // 词法环境
79+
EnvironmentRecord: { // 环境记录
80+
Type: "Declarative", // 函数环境
81+
// 标识符绑定在这里 // 对外部环境的引用
82+
outer: <Global or outer function environment reference>
83+
}
84+
}
85+
```
86+
87+
88+
89+
#### 变量环境
90+
91+
变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性
92+
93+
在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量( `let` 和 `const` )绑定,而后者仅用于存储变量( `var` )绑定
94+
95+
举个例子
96+
97+
```js
98+
let a = 20;
99+
const b = 30;
100+
var c;
101+
102+
function multiply(e, f) {
103+
var g = 20;
104+
return e * f * g;
105+
}
106+
107+
c = multiply(20, 30);
108+
```
109+
110+
执行上下文如下:
111+
112+
```js
113+
GlobalExectionContext = {
114+
115+
ThisBinding: <Global Object>,
116+
117+
LexicalEnvironment: { // 词法环境
118+
EnvironmentRecord: {
119+
Type: "Object",
120+
// 标识符绑定在这里
121+
a: < uninitialized >,
122+
b: < uninitialized >,
123+
multiply: < func >
124+
}
125+
outer: <null>
126+
},
127+
128+
VariableEnvironment: { // 变量环境
129+
EnvironmentRecord: {
130+
Type: "Object",
131+
// 标识符绑定在这里
132+
c: undefined,
133+
}
134+
outer: <null>
135+
}
136+
}
137+
138+
FunctionExectionContext = {
139+
140+
ThisBinding: <Global Object>,
141+
142+
LexicalEnvironment: {
143+
EnvironmentRecord: {
144+
Type: "Declarative",
145+
// 标识符绑定在这里
146+
Arguments: {0: 20, 1: 30, length: 2},
147+
},
148+
outer: <GlobalLexicalEnvironment>
149+
},
150+
151+
VariableEnvironment: {
152+
EnvironmentRecord: {
153+
Type: "Declarative",
154+
// 标识符绑定在这里
155+
g: undefined
156+
},
157+
outer: <GlobalLexicalEnvironment>
158+
}
159+
}
160+
```
161+
162+
留意上面的代码,`let`和`const`定义的变量`a`和`b`在创建阶段没有被赋值,但`var`声明的变量从在创建阶段被赋值为`undefined`
163+
164+
这是因为,创建阶段,会在代码中扫描变量和函数声明,然后将函数声明存储在环境中
165+
166+
但变量会被初始化为`undefined`(`var`声明的情况下)和保持`uninitialized`(未初始化状态)(使用`let`和`const`声明的情况下)
167+
168+
这就是变量提升的实际原因
169+
170+
171+
172+
### 执行阶段
173+
174+
在这阶段,执行变量赋值、代码执行
175+
176+
如果 `Javascript` 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 `undefined` 值
177+
178+
179+
180+
### 回收阶段
181+
182+
执行上下文出栈等待虚拟机回收执行上下文
183+
184+
185+
186+
## 二、执行栈
187+
188+
执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文
189+
190+
![](https://static.vue-js.com/9eda0310-74c1-11eb-ab90-d9ae814b240d.png)
191+
192+
当`Javascript`引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中
193+
194+
每当引擎碰到一个函数的时候,它就会创建一个函数执行上下文,然后将这个执行上下文压到执行栈中
195+
196+
引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文),当该函数执行结束后,对应的执行上下文就会被弹出,然后控制流程到达执行栈的下一个执行上下文
197+
198+
举个例子:
199+
200+
```js
201+
let a = 'Hello World!';
202+
function first() {
203+
console.log('Inside first function');
204+
second();
205+
console.log('Again inside first function');
206+
}
207+
function second() {
208+
console.log('Inside second function');
209+
}
210+
first();
211+
console.log('Inside Global Execution Context');
212+
```
213+
214+
转化成图的形式
215+
216+
![](https://static.vue-js.com/ac11a600-74c1-11eb-ab90-d9ae814b240d.png)
217+
218+
简单分析一下流程:
219+
220+
- 创建全局上下文请压入执行栈
221+
- `first`函数被调用,创建函数执行上下文并压入栈
222+
- 执行`first`函数过程遇到`second`函数,再创建一个函数执行上下文并压入栈
223+
- `second`函数执行完毕,对应的函数执行上下文被推出执行栈,执行下一个执行上下文`first`函数
224+
- `first`函数执行完毕,对应的函数执行上下文也被推出栈中,然后执行全局上下文
225+
- 所有代码执行完毕,全局上下文也会被推出栈中,程序结束
226+
227+
228+
## 参考文献
229+
230+
- https://zhuanlan.zhihu.com/p/107552264

0 commit comments

Comments
 (0)