一个执行上下文的生命周期有下面几个阶段:
- 创建阶段:在执行上下文中创建变量对象、确定作用域和this指向等
- 执行阶段:完成代码执行,其中包括变量赋值,函数引用,执行代码等
- 销毁阶段:可执行代码执行完成后,执行上下文出栈,销毁内存空间
从上面可以看出执行上下文中有三个属性尤为重要:
-
变量对象
-
作用域
-
this
只有充分理解这三个属性,才能够真正理解执行上下文,以及JavaScript是怎么运行的。
变量对象
因为不同的执行上下文下的变量对象有所不同,所以分两个情况进行讨论
全局执行上下文
全局上下文比较特殊,它的变量对象就是全局对象,也就是window对象
全局对象:是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。
- 全局对象会预定义一系列函数和属性
console.log(Math.random())
- 在全局执行上下文中可以通过this调用
var a = 1;
console.log(this.a); // 1
console.log(window.a); // 1
- 其他的执行上下文都可以访问全局对象
var a = 1;
function getName(){
var name = '旺财';
console.log(window.a)
}
getName()
// 结果 1
函数执行上下文
为了更好得理解函数执行上下文中变量对象,将执行上下文分为三个部分。
初始化
初始化变量对象时,会在对象初始化一个arguments属性,其属性值Arguments(一个类数组对象),包括如下属性:
- callee — 指向当前函数的引用
- length — 真正传递的参数个数
- properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。
执行阶段前
变量对象在进入执行阶段之前,经历一下几个过程:
- 初始化函数的所有形参, 属性值为
undefined; - 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。
- 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined,const/let 声明的变量没有赋值,不能提前使用
注意:如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性,函数声明会比变量声明优先级更高一点。
function test(a,b){
console.log(a);
console.log(b);
console.log(c);
console.log(foo());
var c = 20;
function foo(){
return 2;
}
var d = function() {};
}
test(10)
进入test函数的执行上下文创建时,会生成如下的变量对象。
// 创建阶段
VO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
c: undefined,
d: undefined,
foo: <foo reference>, // 表示foo的地址引用
}
执行阶段后
变量对象在进入执行阶段之前,经历一下几个过程:
- 变量对象(Variable Object,VO)转变为活动对象(Active Object)
- 变量赋值
变量对象和活动对象的区别如下:
变量对象和活动对象没有本质区别,都是同一个对象,只是出于执行上下文的不同生命周期。未进入执行阶段前,变量对象中的属性都不能访问,进入执行阶段后,变量对象转变为活动对象,里边的属性都能够被访问,然后开始执行阶段的操作。
// 执行阶段
VO -> AO // Active Object
AO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
c: 20,
d: <d reference>,
foo: <foo reference>, // 表示foo的地址引用
}
因此最开始的代码执行顺序应该如下:
function test(a,b){
function foo(){
return 2;
}
var c;
var d;
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(foo());
c = 20;
d = function() {};
}
test(10)
//输出结果
10
undefined
undefined
undefined
2