什么是 JS 的执行上下文?从行为模型来理解
一、先搞懂:执行上下文是什么
可以把执行上下文想象成一个"容器",JS 引擎每次要跑一段代码之前,都会先搭一个这样的容器出来。容器里分两块:
- Memory component:存变量和函数
- Code component:负责真正执行代码
整个容器的搭建,分两个阶段完成,这是重点。
二、两个阶段:先"占位",再"赋值"
拿一段最简单的代码举例:
var n = 2;
function square(num) {
var ans = num * num;
return ans;
}
var square2 = square(n);
var square4 = square(4);
阶段一:内存创建阶段
引擎会把代码从头到尾扫一遍,不执行任何逻辑,只是提前给每个变量和函数"占位置":
- 遇到变量,先扔一个
undefined占着 - 遇到函数声明,直接把整个函数体原封不动存进去
所以这一步结束后,内存里长这样:
阶段二:代码执行阶段
引擎再从头到尾走一遍,这次是真的在执行了——把计算结果或字面量,填进阶段一占好的坑里。执行到 n = 2 这一行,n 的值就从 undefined 变成了 2。
三、函数调用:每次都会新开一个执行上下文
代码走到第 6 行 square(n),情况变得有意思了。只要看到函数名后面跟一对括号,就说明这是一次函数调用——JS 引擎会专门为这次调用创建一个全新的执行上下文,跟外层(这里是全局)互不干扰。
这个新的执行上下文,同样要走一遍"内存创建 → 代码执行"两阶段:
- 阶段一:给参数
num和局部变量ans占位,都是undefined - 阶段二:实参
2传给num,执行ans = num * num算出4,最后return ans把值带回调用的地方
函数一旦 return,这个专属的执行上下文就会被整个销毁,num、ans 这些值不会保留下来。第 7 行的 square(4) 会完整重复一遍这个过程,只是这次结果是 16。
四、调用栈:谁在管这么多执行上下文
如果代码里全是函数调用,甚至函数里还嵌套函数调用,甚至像递归一样执行上下文可能一层套一层地不断产生。这就要引出调用栈(Call Stack)。
规则很简单:
- 程序一启动,全局执行上下文最先压入栈底
- 每发生一次函数调用,对应的新执行上下文压到栈顶
- 函数执行完、上下文被销毁,立刻从栈顶弹出,控制权交还给栈里的上一层
其实到这一步,大家应该能联想到很多的知识点,比如:
- 闭包:如果一个函数返回一个带有本上下文变量的函数,那执行上下文还会被销毁吗?
- 暂时性死区
- 引用类型和非引用类型 这里就不多说了。
这篇文章的灵感来自我最近看的一个视频:JS是如何工作的 && 执行上下文。视频里的思路很易懂。