执行上下文栈
大家都知道js是单线程的,所以这就意味着它一次只能做一个件事情,而一个js的文件中必然会执行多个操作,比如全局代码,函数代码,每执行一次就会生成多个执行上下文,js中采用栈的方式来处理它,大家都知道栈的执行是先进的后出,因此全局的执行上下文永远是最底层的,在程序执行完毕的时候出栈
我们举个栗子来模拟一下执行上下文栈的流程
<script>
function EC(){
let b = 1
console.log(b)
}
EC()
</script>
1.首先我们把执行上下文栈假设成一个数组,这样比较好理解,当代码还未执行时,执行上文栈是空的
ECStack = [];
2.当上述代码刚进入script标签后,就进入了一个全局的环境,此时的全局环境的执行上下文就会入栈,我们用globalContext来表示全局的执行上下文
ECStack = [
globalContext
]
3.我们接着来看我们的代码,此时js遇到了如下代码
function EC(){
let b = 1
console.log(b)
}
此时编译器遇到了函数,它会生成一个函数的执行上下文并压入栈中,我们用ECContext来表示该函数的执行上下文
//此时的ECStack中多了一个ECContext,而globalContext被压到了栈底
ECStack = [
ECContext,
globalContext
]
4.再接着看我们的代码,此时该执行函数EC了,此时EC的执行上下文内部会做一些处理,这个我们后面会讲到,现在我们只关心执行上文栈是怎么操作的
//当EC函数执行完后,在栈顶的ECContext会弹出,栈底的globalContext会到栈顶的位置,此时的ECStack
ECStack = [
globalContext
]
当该js被销毁的时候,globalContext会弹出栈,ECStack被回收,至此一个完整的js就走完了
大家可能会有些疑问,频频提到的执行上下文究竟做了些什么?
执行上下文(Execution Context)
什么是执行上下文?来看定义
一段可以执行的代码在被执行的时候,会创建一个执行上下文,执行上下文,可以理解为当前代码的执行环境
那么什么是可执行代码呢?
- 全局代码(全局环境):你的代码首次执行的默认环境,例如加载外部的js文件或者本地标签内的代码。全局代码不包括任何function体内的代码。 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
- 函数代码(函数环境):当函数被调用执行时,会进入当前函数中执行环境
- eval(不建议使用,可忽略)
执行上下文(Execution Context)
对于每个执行上下文都有三个重要的属性,它们分别是
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this的指向
执行上下文的变量对象(Variable object,VO))
我们先来分析下变量对象Variable object,VO到底是什么
执行上下文在创建的时候会先创建变量对象vo,VO保存了函数中的所有形参,实参,局部变量,this指针等函数执行时的函数内部的数据情况(此时还没有执行代码),在全局执行上下文中叫做变量对象VO,在函数执行上下文中叫做活动对象AO,其实两个是一样的,具体如下
1.函数的形参和实参(如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参传入,形参就用undefined表示
2.函数的声明,function max()
- 由函数的名称和内容来创建一个变量对象
3.变量的声明,如 var a
- 由名称和undefined来创建一个变量对象
我们还用分析执行上下文栈时的代码改造下来举栗子
function EC(a) {
var b = 1
console.log(b)
console.log(a)
var c = function (){}
function c(){}
}
EC(2)
此时的AO为,函数还没有被执行
AO = {
arguments:{
0:2,
length: 1
},
a:2,
b:undefined,
c:undefined,
d:reference to function d(){}
}
当执行EC函数的代码时,AO会依次赋值,当执行完毕后,此时的AO
AO = {
arguments:{
0:2,
length: 1
},
a:2
b:1,
c:reference to FunctionExpression "c"
d:reference to function c(){}
}
至此变量对象的创建过程就完结了