函数调用栈

Thursday, December 24, 2020

JavaScript 使用函数调用栈来管理所有函数的执行

函数体

函数体也是数据,每一个函数体都是一个对象,在代码解析阶段创建,被持久的存储于内存之中,也就是说,在程序的运行过程中,函数体始终存在。因此,我们可以在程序运行的任何时候调用该函数

执行上下文

声明函数和执行函数是两个不同的阶段

声明函数会创建函数体,函数体会持久的占用一定的内存空间

执行函数会创建执行上下文,执行上下文会占用的内存空间,执行上下文的内存空间是临时的,当函数执行完毕后就被释放回收

一个函数可以调用多次,每次调用都会生成新的执行上下文,常说的函数执行完毕内存被回收,是指执行上下文的内存空间被回收

JavaScript 代码的执行,必须进入到一个执行上下文中:执行上下文可以理解为当前可执行代码的运行环境

JavaScript 中的运行环境包括三种情况:

  • 全局环境:代码运行时会首先进入全局环境,同时会生成全局上下文
  • 函数环境:当函数被调用执行时,进入函数环境执行函数代码,同时该函数对应的执行上下文被创建
  • eval环境:不建议使用,不做介绍

调用栈

当调用一个新函数时,新的执行上下文就被创建,因此一个应用程序中会用大量执行上下文,JavaScript引擎采用栈的方式管理与跟踪多个执行上下文的运行情况,可以称其为函数调用栈

在应用程序的运行过程中,栈底永远是全局上下文,并且不会出栈。栈顶是当前正在执行的上下文,并且正在执行的上下文始终都会在栈顶。也就意味着,无论任何时候,都只会有一个上下文正在执行。在执行过程中,如果遇到了新的函数,那么就会创建新的上下文,推入到栈顶,栈顶上下文执行完毕之后,就会出栈,并被垃圾回收器回收,新的栈顶上下文继续执行。

var color = 'blue';

function changeColor() {
  var anotherColor = 'red';

  function swapColors() {
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
  }

  swapColors();
}

changeColor();

在浏览器环境下,代码包含在 script 标签中,浏览器会将 script 标签处理成为一个匿名函数参与到栈中。但它不是全局上下文。并且当我们在 chrome 中通过调试工具观察函数调用栈时,看不到全局上下文入栈。这也是默认全局上下文始终存在。因此,在浏览器的观察中,更准确的表达应该如下图所示

img

function f1() {
  var n = 999;
  function f2() {
    alert(n);
  }
  return f2;
}
var result = f1();
result(); // 999

栈图

函数调用栈的变化过程,只栈的方向是栈顶朝下,栈底在上。这也符合栈空间的分配原则。在之前我们提到过,栈空间的分配,是从高位向下分配。