Revan1i

vuePress-theme-reco revan1i    2019 - 2020
Revan1i Revan1i

Choose mode

  • dark
  • auto
  • light
Home
TimeLine
Category
  • javascript
  • css
  • react
  • js
  • how
  • math
  • regexp
  • algorithm
  • feeling
Tag
Contact
  • GitHub
author-avatar

revan1i

25

文章

20

标签

Home
TimeLine
Category
  • javascript
  • css
  • react
  • js
  • how
  • math
  • regexp
  • algorithm
  • feeling
Tag
Contact
  • GitHub

Javascript基础之执行上下文栈

vuePress-theme-reco revan1i    2019 - 2020

Javascript基础之执行上下文栈


revan1i 2019-10-23 16:09:19 javascript base

众所周知,JavaScript 代码并不是按照从上到下按照顺序执行,存在变量提升和函数提升。我们先来看下什么是变量提升和函数提升。

# 变量提升

通常 JavaScript 引擎在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升到当前作用域的顶端,然后进行接下来的处理。

console.log(a);  // undefined
var a = 1;
1
2

运行代码,会发现打印的结果是 undefined ,在变量还没有出现之前就调用,为什么不会出错呢?这是因为 JS 经过一次预编译之后,上面的代码等价于:

var a;
console.log(a);
a = 1;
1
2
3

a 只有声明的情况下初始值就是 undefined。所以不会出错而打印 undefined。

# 函数提升

同样的,JavaScript 引擎也会把函数声明提升到当前作用域的顶部。举个例子:

hoisted() // I am hoisted
function hoisted() {
  console.log('I am hoisted')
}
1
2
3
4

综上,JavaScript 引擎并不是一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个“准备工作”,那么这个“一段一段”中的“段”是怎么划分的?“准备工作“又是什么?

# 可执行代码

这就要说到 JavaScript 的可以执行代码有那些了?
总共有三种类型:全局代码、函数代码、eval代码。
当 JS 引擎执行到一个函数时,就会进行”准备工作“,这里的”准备工作“专业一点的说法,就叫做”执行上下文(execution context)“。 简而言之,执行上下文就是当前 JavaScript 的代码被解析和执行时所在环境的抽象概念,JavaScript 中运行任何的代码都是在执行上下文中运行。

# 执行上下文栈

为了存储和管理创建的执行上下文,JavaScript 引擎创建了执行上下文栈(Execution context stack, ECS)。
我们定义执行上下文栈为一个数组来模拟执行上下文栈的行为。

ECStack = [];
1

当 JavaScript 开始解释执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先向执行上下文压入一个全局执行上下文,用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会清空,所以程序在结束之前,ECStack 底部永远有一个 globalContext。

ECStack = {
  globalContext
}
1
2
3

假设 JavaScript 遇到下面的一段代码

function func3() {
  console.log('func3')
}
function func2() {
  console.log('func2')
  func3()
}
function func1() {
  console.log('func1')
  func2()
}
func1()
1
2
3
4
5
6
7
8
9
10
11
12

当执行一个函数时就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕之后,则会将执行上下文从执行上下文栈弹出。知道工作原理之后,我们看看 JavaScript 引擎是怎么处理上面这段代码:

// func1()
ECStack.push(<func1> functionContext);

// func1中还调用了func2,创建一个func2的执行上下文
ECStack.push(<fun2> functionContext);

// func2调用了func3,创建一个func3的执行
ECStack.push(<fun3> functionContext);

// func3执行完毕
ECStack.pop()

// func2执行完毕
ECStack.pop()

// func1执行完毕
ECStack.pop()

// ...继续执行接下来的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19