一、Javascript变量对象
在JS代码中声明的所有变量都保存在变量对象(Variable Object)中,除此之外,变量对象种还包括以下内容:
1、函数的所有参数(Firefox中为参数对象arguments)
2、当前上下文中的所有函数声明(通过function声明的函数)
3、当前上下文的所有变量声明(通过var声明的变量)
-
创建过程
在Chrome浏览器中变量对象首先获得函数的参数变量及其值;在Firefox浏览器中,是直接将参数对象arguments保存在变量对象中。
依次获取当前上下文的所有函数声明,也就是使用function关键字声明的函数。在变量对象中会以函数名建立一个属性,属性值为指向该函数所在的内存地址引用。如果函数名的属性已经存在,那么该属性的值会被新的引用覆盖。
依次获取当前上下文中的变量声明,也就是使用var关键字声明的变量。每找到一个变量声明,就在变量对象中就以变量名建立一个属性,属性值为undefined,则会直接跳过,原属性值不会被修改。
ES6支持新的变量声明方式let/const,规则与var 完全不同,它们是在上下文的执行阶段开始执行的,避免了变量提升带来的一系列问题,
具体执行过程:var a = 30;
首先执行上下文的创建阶段会先确认变量对象,而变量对象的创建过程则是先获取变量名并赋值为undefined,因此第一步是:
var a= undefined;
上下文的创建阶段完毕后,开始进入执行阶段,在执行阶段需要完成变量赋值工作,因此第二步是:a = 30;
需要注意的是,这两步分别是在上下文的创建阶段和执行阶段完成的,因此var a= undefined 这一步是提前到了比较早的地方去执行,下面通过一个简单的例子来证明。
console.log(a);
var a =30;
undefined
//创建阶段
var a=undefined
//执行阶段
console.log(a);
a=30;
这种现象称之为变量提升。
可以看出,在变量对象的创建过程中,函数声明的执行优先级会比变量声明的优先级更高一点,而且同名的函数会覆盖函数与变量,但是同名的变量不会覆盖函数。
但是在上下文的执行阶段,同名的函数会被变量重新赋值。
var a=20;
function fn(){console.log('fn')};
function fn(){console.log('cover fn.')};
function a(){console.log('cover a.')};
console.log(a);
fn();
var fn = 'I want cover function namedcfn.';
console.log(fn);
创建阶段
function fn(){console.log('fn')};
function fn(){console.log('cover fn.')};
function a(){console.log('cover a.')};
var a= undefined;
var fn=undefined;
执行阶段
console.log(a);
fn();
var fn = 'I want cover function namedcfn.';
console.log(fn);
- 实例分析
function test(){
conslole.log(a);
console.log(foo());
var a=1;
function foo(){
return 2;
}
}
test();
运行test函数时,对应的上下文开始创建,可以用如下形式表达这个过程;
//创建过程
testEC={
VO:{}, //变量对象
scopeChain:[], //作用域链
this:{ }
}
VO={
arguments:{...},
foo:<foo reference>,
a:undefined
}
在函数调用栈中,如果当前上下文处于函数调用栈的栈顶,则意味着当前上下文处于激活状态,此时变量对称为活动对象(AO ,Activation Object).活动对象中包含变量对象的所有属性;并且此时所有的属性都已经完成了赋值,除此之外,活动对象还包含了this的指向。
//执行阶段
vo->AO
AO={
arguments:{},
foo:<foo reference>,
a:1,
this:window
}
可以通过断点调式的方法在Chrome开发者中查看这一过程,
因此上面的例子实际执行顺序如下:
function test(){
function foo(){
return 2;
}
var a=undefined;
conslole.log(a);
console.log(foo());
a=1;
}
test();
- 全局上下文的变量对象
以浏览器为例,全局对象为window对象
全局上下文的变量对象有一个特殊的地方,即它的变量对象就是window对象,而且全局上下文的变量对象不能变成活动对象,
//以浏览器为例,全局对象为window
//全局上下文
windowEC={
VO:window,
scopeChain:{ },
this:window
}
除此之外,全局上下文的生命周期与程序的生命周期一致,只要程序运行不结束(比如关掉浏览器窗口),全局上下文就会一直存在,其他所有的上下文环境都能直接访问全局上下文的属性。