JavaScript运行机制解析(一)执行上下文

109 阅读2分钟

JavaScript运行环境简介

在讨论JavaScript执行上下文的之前,我们先来思考一个问题,JS在运行一段代码的时候,它的执行顺序是如何的?

顺序执行

var foo = function () {
    console.log('foo1');
}
​
foo();  // foo1var foo = function () {
    console.log('foo2');
}
​
foo(); // foo2

然鹅,再去看这段代码

function foo() {
    console.log('foo1');
}
​
foo();  // foo2function foo() {
    console.log('foo2');
}
​
foo(); // foo2

输出的结果确实两个foo2

因为 JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当运行一段代码的时候,会进行一个“准备工作”,比如第一个例子中的变量提升,和第二个例子中的函数提升。

但是本文真正想让大家思考的是:这个“一段一段”中的“段”究竟是怎么划分的呢?

而JS引擎是又是如何进行“准备工作”的?

一、可执行代码

这里要先提到JavaScript中的可执行代码类型有哪些了?

分别是:全局代码、函数代码、eval代码

当运行一段函数代码的时候,首先会先进行“准备工作”,即创建“执行上下文”。

执行上下文是JavaScript执行一段代码时的运行环境。 比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this变量对象以及函数等。

二、执行上下文栈

在实际项目的运行代码中,我们存在那么多可执行的代码块,那JS到底只如何管理多个执行上下文呢?

答案是:JS会创建执行上下文栈(Execution context stack,ECS)来进行管理复杂的执行上下文,俗称调用栈

我们来模拟一下执行上下文栈的行为:

function fun3() {
    console.log('fun3')
}
​
function fun2() {
    fun3();
}
​
function fun1() {
    fun2();
}
​
fun1();

定义一个数组作为执行上下文栈:

ECStack = [];

我们要知道,默认情况下,JS在运行的时候,栈底总会存在一个全局上下文,我们称为globalContext。即:

ECStack = [
  globalContext
]

当JavaScript执行上面代码的时候,会创建对应的执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会讲函数的执行上下文从栈顶弹出。

// 伪代码// fun1()
ECStack.push(<fun1> functionContext);
​
// fun1中竟然调用了fun2,还要创建fun2的执行上下文
ECStack.push(<fun2> functionContext);
​
// 擦,fun2还调用了fun3!
ECStack.push(<fun3> functionContext);
​
// fun3执行完毕
ECStack.pop();
​
// fun2执行完毕
ECStack.pop();
​
// fun1执行完毕
ECStack.pop();
​
// ECStack底层永远有个globalContext

那具体是不是这样子呢,我们用可视化工具来测试一下:https://www.jsv9000.app/ stack.gif

了解了执行上下文基本概念之后,接下来我们再来探究执行上下文里面包含了哪些内容~