当一段代码被执行时,JS引擎先会对其进行编译,并创建执行上下文 ----接上篇JS变量提升
JS变量提升已经讲了编译和执行。但没有明确什么才叫 一段代码
以下情况代码才算是“一段”代码,才会在执行之前就进行编译并创建执行上下文
- 当JS执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
- 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
- 当使用eval函数的时候,eval 的代码也会被编译,并创建执行上下文。
函数调用
分析以下代码
<script type="text/javascript">
var a = 1
function add () {
var b = 2
return a+b
}
add()
</script>
1.编译阶段
创建全局执行上下文
变量环境:
variableEnvironment:
a => undefined,
add => function: {var b = 2;return a+b}
可执行代码:
a = 1
add()
2.执行阶段:
a = 1
add()
当执行到add()这儿时,js判断这是一个函数调用。将执行以下操作
1.从 全局执行上下文中 取出add函数
2.对add函数这“一段”代码进行编译,并创建 该函数的执行上下文 和 可执行代码
3.编译结束,执行这“一段”代码
执行到add()时,就有了两个执行上下文, 全局执行上下文 和 add函数的执行上下文
所以JS代码是可以有多个执行上下文的, 通过一种叫 栈 的数据结构来管理
栈
举个例子, 一条堵住的小巷子,车辆A开进去发现堵住了,后面车辆B也开进去了。想要开出来这个小巷子,只能车辆B先倒车出来然后才是车辆A
车辆开进去的过程是 入栈
车辆倒车出来的过程是 出栈
这个一端堵住的小巷子可以认为是 栈容器
每一个车辆就是栈元素, 栈元素满足后进先出
调用栈:
调用栈就是用来管理函数调用关系的一种数据结构
JS利用这种栈结构来管理执行上下文,通常把这种用来管理执行上下文的栈称为执行上下文栈
分析下面代码
<script>
var a = 1
function add(b,c){
return b + c
}
function addAll(b,c){
var d = 2
var result = add(b,c)
return a + result + d
}
addAll(3,4)
</script>
一.创建全局上下文,并将其压入栈底,并执行
编译阶段执行上下文中的变量环境
var a = undefined
function add () {...}
function addAll () {...}
执行阶段代码有
a = 1
addAll(3,4)
当执行到 addAll(3,4) 时
二.从全局执行上下文中取出addAll函数并编译执行并为其创建一个执行上下文, 且压入调用栈中
编译阶段执行上下文中的变量环境
var d = undefined
var result = undefined
执行阶段代码有
d = 2
result = add(b,c)
return a + result + d
当执行到 result = add(b,c) 时
三.从全局执行上下文中取出add函数并编译执行并为其创建一个执行上下文, 且压入调用栈中
执行阶段代码只有 return a + b
- add 函数返回时,该函数的执行上下文就会从栈顶弹出(出栈),并将add函数的返回值7赋值给result
- addAll函数继续往下执行, 执行return语句返回10的时候,该函数的执行上下文就会从栈顶弹出(出栈)
- 然后继续执行全局上下文的执行代码, addAll函数返回10
利用浏览器查看调用栈信息
可以通过console.trace()方法来输出当前的函数调用关系
也可以打开“开发者工具”,点击“Source”标签,可以通过右边“call stack”来查看当前的调用栈的情况
栈溢出
调用栈是有大小的
当入栈的执行上下文超过一定数目,JS引擎就会报错,我们把这种错误叫做栈溢出。
如何解决栈溢出问题
1. 使用尾递归调用,有兼容问题
2. 不使用递归函数,不进入调用栈就不存在栈溢出