深入JS之我是如何搞懂调用栈的

181 阅读3分钟

前言

在执行一段代码时,JavaScript 引擎会先对其进行编译,并创建一个执行上下文。但是哪些情况下才符合‘一段代码’,才会在执行之前就进行编译并创建执行上下文。一般来说有三种情况:

  1. 当 JavaScript 在执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面生存周期内,全局执行上下文只有一份。
  2. 当 JavaScript 在执行调用函数时,函数体内的代码会被编译,并创建执行上下文,一般情况下函数执行结束之后,创建的函数执行上下文会被销毁。
  3. 当使用 eval 函数时候,eval 的代码也会被编译,并创建执行上下文。

在了解了执行上下文之后,在此基础之上来继续深入了解调用栈

调用栈

在编写 JavaScript 代码有时候会出现栈溢出的错误,如下图:

那为什么会出现这样的错误呢?这就涉及到调用栈的内容。JavaScript 中有很多函数,经常会出现在一个函数中调用另外一个函数的情况,调用栈就是用来管理函数调用关系的一种数据结构。因此要弄清楚调用栈得先了解函数调用和栈结构。

函数调用

函数调用就是运行一个函数,具体使用是函数名后面加一个小括号,下面是简单得示例代码:

var a = 1
function add() {
    var b = 2
    return a + b
}
add()

上面代码,先是创建一个函数,接着在代码最下面调用该函数。那么下面将使用这段代码解释函数调用得过程。

在执行到函数 add() 之前, JavaScript 引擎会为上面这段代码创建全局执行上下文,包含了声明得函数和变量,可以参考下图:

从图中可以看出,代码中全局变量和函数都保存在全局上下文的变量环境中。执行上下文准备好之后,便开始执行全局代码,当执行到 add 这儿时,JavaScript 判断这是一个函数调用,那么将执行以下操作:

  • 首先,从全局执行上下文中,取出 add 函数代码。
  • 其次,对 add 函数的这段代码进行编译,并创建该函数的执行上下文和可执行代码。
  • 最后,执行代码,输出结果。

也就是说执行 JavaScript 时可能存在多个执行上下文,而 JavaScript 是用栈来管理这些执行上下文。

那什么是栈?它是如何管理执行上下文呢?

什么是栈

栈是一种数据结构,栈中得元素满足后进先出的特点。如下图所示:

什么是 JavaScript 调用栈

JavaScript 引擎正是利用栈的这种结构来管理执行上下文的。在执行上下文创建好后,JavaScript 引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文栈,又称调用栈。

我们详细分析上面的例子:

  • 第一步:创建全局执行上下文,并将其压入栈底
  • 第二步:调用 add 函数,当调用该函数时,JavaScript 引擎会编译该函数,并为其创建一个执行上下文,最后还将该执行上下文压入栈中。

完整的调用栈可以参考下图

至此,整个 JavaScript 流程执行结束了。

好了,现在你应该知道了调用栈是 JavaScript 引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系。

参考文章

浏览器工作原理与实践