在前面的文章中我们提到过变量对象(Variable Object),我们在Js代码中申明的所有变量都保存在变量对象当中,除此之外变量对象中还可能包含以下内容。
(1)函数的参数(arguments,es6中废弃)。
(2)当前执行上下文的所有函数申明(通过function申明的函数)。
(3)当前上下文的所有变量申明(通过var申明的变量)。
一,创建过程
变量对象的创建依次经历了一下过程
1,在chrome浏览器中,变量对象会首先获得函数的参数及其值,在Firefox浏览器中,是直接将参数对象arguments保存在变量对象中。
2,依次获取当前执行上下文的所有函数申明,也就是function申明的函数,在变量对象中会以函数名建立一个与之相对应的属性,属性值为指向所在内存地址的引用,如果函数名的属性已经存在,那么属性的值会被新的引用覆盖。
3,依次获取当前执行上下文的所有变量申明,也就是使用var关键字申明的变量。每找到一个申明就在变量对象当中以变量名建立一个属性,属性值为undefined,如果该变量的属性已经存在,为了防止同名函数被修改为undefined,则会直接跳过去,原属性值不会被修改
ES6支持新的变量申明方式,规则与var完全不同,他们是在上下文执行阶段开始执行的,避免了变量提升带来的一系列问题。
我们知道了上面的规则之后,我们思考一个问题,当我们执行上下文代码的时候具体的执行过程是怎么样的呢?
var a=20;
首先上下文阶段会先确认变量对象,而变量对象的创建过程则是先获取变量名并赋值为undefined,因此第一步是:
var a = undefined;
上下文的创建阶段完毕后,开始进入执行阶段,在执行阶段需要完成变量的赋值工作,因此第二步是:
a =30;
需要注意的是,这两步分别是在上下文的创建阶段与执行阶段完成的因此var a=undefined这一步其实提前到了比较早的地方取执行,下面通过一个简单的列子来证明。
console.log(a);//这个时候的a输出的结果为undefined。
var a = 30;
结合之前的解释,这个列子的实际执行顺序为
//c创建阶段
var a = undefined;
//执行阶段
console.log(a);
a=30;
这种现象被称为变量提升(Hoisting).
从上面的例子我们可以看出,在变量对象创建的过程当中,函数申明的执行优先级会比变量的优先级高一些,而且同名的函数会覆盖函数与变量,同名的变量不会覆盖函数。但在上下文的执行阶段,同名函数会被变量重新赋值。
var a=20;
function fn(){console.log('fn')};
function fn(){console.log('conver fn')};
function fn(){console.log('conver a')};
consle.log(a);
fn();
var fn = 'I vant conver function named fn';
console.log(fn);
//20;
//conver a
//I vant conver function named fn
上面代码的执行顺序为
//创建阶段
function fn(){console.log('fn')};
function fn(){console.log('conver fn')};
function fn(){console.log('conver a')};
var a =undefined;
var fn=undefined;
//执行阶段
a=20;
consle.log(a);
fn();
fn='I vant conver function named fn';
consle.log(fn);
根据输出结果可以证明,在创建阶段,后创建的函数会覆盖前面创建的函数,但是后创建的变量fn并没有覆盖前面创建的函数fn。而在执行阶段,a与fn的重新赋值导致他们发生了变化。
二,实列分析
为了更加深刻的理解变量对象,下面结合一些简单的列子来进行探讨。
//demo1
function test(){
console.log(a);
console.log(foo());
var a=1;
function foo(){
return 2;
}
}
test();
运行test函数时,对应的执行上下文开始创建,可以用如下形式来表示整个过程。
//c创建过程
testEC={
VO:{},//变量对象
scopeChain:[],//作用域链
this:{}
}
//这里我们暂时不分析作用域链与this,后续章节会详细讲解
//VO为Variable Object的缩写,既变量对象
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;
console.log(a);
console.log(fn());
a=1;
}
test();
三,全局上下文的变量对象
以浏览器为列,全局对象为window对象。
全局上下文的变量对象有一个特殊的地方,既它的变量对象就是window对象,而且全局上下文的变量对象不能变成活动对象。
//以浏览器为列,全局对象为window。
//全局上下文
windowEC={
VO:window,
scopeChain:{},
this:Window,
}
除此之外,全局上下文的生命周期与程序的生命周期一样,只要程序运行不结束。全局上下文就会一直存在,其他的上下文环境都能直接访问到全局上下文的属性。
..............................................
下一章我们讲讲作用域与作用域链
..................................................