深入JS之执行上下文
什么是上下文?
上下文的定义是:上下文就是程序需要执行时所需要的环境,具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。
从以上的概念中可以看出,上下文无非就是存储一些信息,包括变量,内存信息等。
什么是执行上下文?
当JS代码执行到一段可执行的的代码(全局代码,函数代码 eval代码)时候,就会做一系列的准备工作。这些准备工作包括以下几个方面:
- 创建变量对象
- 绑定作用域链
- 绑定this
以上操作即是执行上下文所做的事情,执行上下文说的通俗一点就是帮程序管理好变量与内存等。
执行上下文的工作可以分为两部分:
- 创建过程
- 执行过程
创建过程
创建过程主要所做的事情,就是申请内存地址,用来存储变量对象VO(Variable object):
- 对于函数声明的所有形参,会以
参数名: 参数值
存储到变量对象VO中去 - 对于函数声明,会以
函数名: function-object
存储到变量对象VO中去 - 对于普通的声明,比如:var, let等,他会先判断VO中有没有与这个变量名一样的函数声明或普通声明,如果有的话,就不做更改,如果没有的话,那么就会添加进去VO中去。不会进行初始化
- 绑定作用域链scope(本文不做具体展开,就是因为绑定了作用域链,所以才会有作用域的链式访问,如果一个AO中没有某个属性,就会去他的作用域上面去找。类似于原型链)
举个例子:
function test(a, d) {
function b(){};
var b = 1;
var c = 1;
}
test(1, 2)
在上面的函数中,首先会将形参a
形参的
以a:1,d:2
的形式存储到VO中的中去,并在arguments存储{ 0: 1, 1: 2 }
。这也就是为啥可以通过arguments拿到所有参数的原因。
接下来遇到了函数声明b,那么会以b:function-object
存储到VO中去,
接下来遇到了普通声明b,但是在VO中已经有一个函数声明为b了,按照他的规则,他不会对现有的进行更改,因此他不会被添加到VO中去,因此此时VO中的b还是一个函数声明
接下来遇到了普通声明c,在VO中没有任何一个声明是c,将c以c:undefined
的形式插入到VO中,
因此最后的VO就是:
VO: {
arguments: {
0: 1,
1: 2,
},
a: 1,
d: 2,
b: reference to function c(){},
}
执行阶段
在初始化之后,进入代码执行阶段的时候,就会根据顺序对函数中的变量进行修改,此时,VO就变成了活跃对象AO,只有是活跃对象才可以被访问,因此此时变成了
AO: {
arguments: {
0: 1,
1: 2,
},
a: 1,
d: 2,
b: reference to function c(){},
}
在函数的前2行都是没有进行任何赋值操作的,在函数的第三行将b重新赋值成了数字类型,因此此时的AO就变成了
AO: {
arguments: {
0: 1,
1: 2,
},
a: 1,
b: 1,
d: 2,
}
当函数执行完成之后,那么这个执行上下文就会从栈中弹出,如果没有其他函数引用该AO中的变量的话,那么整个AO就会被销毁,如果有的话,则除了被引用的,其余的都销毁。
练习题
console.log(foo) // function foo(){}
function foo(){
}
var foo = 1;
console.log(foo) // 1
/*
当进入执行上下文的时候,会先进入创建阶段,创建阶段的VO格式如下
VO: {
arguments: {
},
foo: reference to function c(){},
},
然后进入执行阶段,执行阶段的时候VO激活成AO,在执行第一行的console.log的时候,因为此时的AO中的foo还是一个函数声明,因此打印function foo(){}
函数执行到var foo = 1;会对AO中的变量foo赋值,值为1,因此此时的AO为
AO: {
arguments: {
},
foo: 1, // var foo = 1;重新赋值
},
在执行最后一行的consle.log的时候,此时foo已经变成了一个数字类型,因此打印的是1
*/