开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
函数调用栈对于代码的执行顺序有着非常重要的影响,在一个js程序的执行过程中,我们需要调用大量的函数来完成程序的运行,js使用函数调用栈来管理所有函数的运行。
函数体
当我们在代码中创建一个函数:
function fn() {}
无论使用什么方式,都可以创建一个函数体,通过原型链,我们知道其实每一个函数都是一个对象,在创建过程中被齿距的存储在内存中,也就是说,在程序运行时,函数体是一直存在的,也是因为这样我们才能在程序运行的任何时间调用函数。
执行上下文
函数的声明和函数的执行是两个完全不同的概念。
声明函数会创建函数体,函数体会持续性的占据内存空间,而执行函数会创建执行上下文,执行上下文又会占用其他的内存空间。执行上下文的作用是实时的记录函数在执行过程中的所有状态和数据,它占用的空间是临时的,当函数执行完成它就会被销毁,释放出所占用的内存空间。
js代码的执行必须进入到一个执行上下文中,执行上下文其实也就是当前代码所处的运行环境,它可以跟踪可执行代码的执行状态。因此同一个函数体可以生成多个执行上下文,每次调用都会生成新的执行上下文,调用完毕执行上下文就会被垃圾回收器回收。
js中的运行环境大致包括两种情况:
- 全局环境:代码运行时首先进入全局环境,同时会生成全局上下文
- 函数环境:当函数被调用执行时,进入函数环境执行函数代码,同时该函数对应的执行上下文被创建
每当我们调用一个新的函数时,一个新的执行上下文就会被创建。因此每一个应用程序中,会有大量的执行上下文创建出来,js引擎使用栈的方式管理和跟踪多个执行上下文的运行情况,我们称之为函数调用栈,在程序运行的过程中,栈底永远都是全局上下文,并且不会出栈,栈顶则是当前正在执行的上下文,并且正在执行的上下文永远在栈顶,也就说明,无论什么时间,都只会有一个上下文的执行,在一个函数执行的过程中,如果遇到了新的函数,那么会继续创建新的上下文,并放入栈顶同时执行,执行完毕后就会出栈并被垃圾回收器回收,新的上下文继续执行,直到全局上下文。
我们通过一个案例来分析一下:
let age = 18
function changeAge() {
let anotherAge = 20
function swapAges() {
const tempAge = anotherAge
anotherAge = color
color = tempAge
}
swapAges()
}
changeAge()
我们使用一张图片来解释整个过程:
- 第一步:全局上下文入栈,一直存在于栈底
- 第二步:代码开始执行,执行到changeAge函数,创建执行上下文并放入栈顶并开始执行,此时全局上下文会挂起,暂停执行。
- 第三步:执行changeAge的途中遇到了swapAge,于是创建新的上下文入栈,此时changeAge上下文变为挂起,暂停执行。
- 第四步:swapAge执行过程中没有新的函数,因此没有新的上下文生成,swapAge函数执行完成,该函数从栈中弹出,等待回收。
- 第五步:changeAge函数重新成为栈顶元素,开始执行,没有新的函数调用,执行完毕后从栈中弹出并回收,此时函数调用栈中只剩下了全局上下文。