调用栈
在前面,我们讲到一段代码被执行时,JS引擎会对其进行编译,并创建执行上下文,那么什么样的代码才符合规范呢?
一般有三种情况,会创建执行上下文:
- 当JavaScript执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份
- 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁
- 当使用eval函数的时候,eval的代码也会被编译,并创建执行上下文
知道这些之后,我们再来学习一下调用栈
什么是函数调用
我们先来看一段代码:
var a = 2
function add(){
var b = 10
return a+b
}
add()
这段代码就是先创建一个函数,然后在下面再调用这个函数
根据我们上一节所讲的执行上下文,在这里,JS引擎会为上面这段代码创建全局执行上下文,包含了声明的函数和变量,保存在全局上下文的变量环境中,如图:
执行上下文准备好后,就开始执行全局代码,执行到add这里的时候,JS发现这是一个函数调用,那么就会执行以下操作:
- 首先,从全局执行上下文中,取出
add函数代码 - 其次,对
add这段代码进行编译,并创建该函数的执行上下文和可执行代码 - 最后,执行代码输出结果
注意,在第二步的时候又新建了一个执行上下文,详细流程如下:
这样,我们就有两个执行上下文了——全局执行上下文和
add函数的执行上下文
而管理这些执行上下文的方法,就是使用栈来管理的
什么是JS调用栈
JS引擎通过调用栈来管理执行上下文
在执行上下文创建好后,JS引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文栈,又称调用栈
这里可以看一段代码:
var a = 2
function add(b,c){
return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return a+result+d
}
addAll(3,6)
代码执行过程中会逐步添加执行上下文:
执行到这里之后,函数add执行完返回时,该函数的执行上下文就会从栈顶弹出,其他以此类推
所以,调用栈是JS引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系
栈溢出
调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript引擎就会报错,我们把这种错误叫做栈溢出
递归代码很容易出现栈溢出的情况,如:
function runStack (n) {
if (n === 0) return 100;
return runStack( n- 2);
}
runStack(50000)
这段代码假如输入较大的数,就可能造成栈溢出,优化(利用循环) :
function runStack(n) {
while (true) {
if (n === 0) {
return 100;
}
if (n === 1) { // 防止陷入死循环
return 200;
}
n = n - 2;
}
}
console.log(runStack(50000));
\