JavaScript运行环境简介
在讨论JavaScript执行上下文的之前,我们先来思考一个问题,JS在运行一段代码的时候,它的执行顺序是如何的?
顺序执行
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
然鹅,再去看这段代码
function foo() {
console.log('foo1');
}
foo(); // foo2
function 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/
了解了执行上下文基本概念之后,接下来我们再来探究执行上下文里面包含了哪些内容~