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 中通过调试工具观察函数调用栈时,看不到全局上下文入栈。这也是默认全局上下文始终存在。因此,在浏览器的观察中,更准确的表达应该如下图所示
function f1() {
var n = 999;
function f2() {
alert(n);
}
return f2;
}
var result = f1();
result(); // 999
栈图
函数调用栈的变化过程,只栈的方向是栈顶朝下,栈底在上。这也符合栈空间的分配原则。在之前我们提到过,栈空间的分配,是从高位向下分配。