什么是 JS 的执行上下文 ?从行为模型理解执行上下文

0 阅读3分钟

什么是 JS 的执行上下文?从行为模型来理解

一、先搞懂:执行上下文是什么

可以把执行上下文想象成一个"容器",JS 引擎每次要跑一段代码之前,都会先搭一个这样的容器出来。容器里分两块:

  • Memory component:存变量和函数
  • Code component:负责真正执行代码

image.png

整个容器的搭建,分两个阶段完成,这是重点。

二、两个阶段:先"占位",再"赋值"

拿一段最简单的代码举例:

var n = 2;
function square(num) {
  var ans = num * num;
  return ans;
}
var square2 = square(n);
var square4 = square(4);

阶段一:内存创建阶段

引擎会把代码从头到尾扫一遍,不执行任何逻辑,只是提前给每个变量和函数"占位置":

  • 遇到变量,先扔一个 undefined 占着
  • 遇到函数声明,直接把整个函数体原封不动存进去

所以这一步结束后,内存里长这样:

image.png

阶段二:代码执行阶段

引擎再从头到尾走一遍,这次是真的在执行了——把计算结果或字面量,填进阶段一占好的坑里。执行到 n = 2 这一行,n 的值就从 undefined 变成了 2

三、函数调用:每次都会新开一个执行上下文

代码走到第 6 行 square(n),情况变得有意思了。只要看到函数名后面跟一对括号,就说明这是一次函数调用——JS 引擎会专门为这次调用创建一个全新的执行上下文,跟外层(这里是全局)互不干扰。

这个新的执行上下文,同样要走一遍"内存创建 → 代码执行"两阶段:

  • 阶段一:给参数 num 和局部变量 ans 占位,都是 undefined
  • 阶段二:实参 2 传给 num,执行 ans = num * num 算出 4,最后 return ans 把值带回调用的地方

image.png

函数一旦 return,这个专属的执行上下文就会被整个销毁,numans 这些值不会保留下来。第 7 行的 square(4) 会完整重复一遍这个过程,只是这次结果是 16

四、调用栈:谁在管这么多执行上下文

如果代码里全是函数调用,甚至函数里还嵌套函数调用,甚至像递归一样执行上下文可能一层套一层地不断产生。这就要引出调用栈(Call Stack)

规则很简单:

  • 程序一启动,全局执行上下文最先压入栈底
  • 每发生一次函数调用,对应的新执行上下文压到栈顶
  • 函数执行完、上下文被销毁,立刻从栈顶弹出,控制权交还给栈里的上一层

image.png

其实到这一步,大家应该能联想到很多的知识点,比如:

  • 闭包:如果一个函数返回一个带有本上下文变量的函数,那执行上下文还会被销毁吗?
  • 暂时性死区
  • 引用类型和非引用类型 这里就不多说了。

这篇文章的灵感来自我最近看的一个视频:JS是如何工作的 && 执行上下文。视频里的思路很易懂。