一、变量对象的创建
之前提到执行上下文的生命周期,里面的所有过程都很重要,再复习一遍:
首先是创建过程中的创建变量对象(Variable Object)。
变量对象的创建,依次经历了以下过程:
1.建立arguments对象。检查上下文中的参数,建立该对象下的属性和属性值。
2.检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性值,属性值为指向该函数所在内存地址的引用。如果该函数名的属性已经存在,那么会被新的引用所覆盖。
3.检查当前上下文中的变量声明。每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量属性名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性不会被更改。
注意,以上只适用于属于变量对象的创建过程。而变量对象是属于执行上下文中的一个部分。
在博客中看到的例子,之前一直没太明白,今天再好好理解一下。以下两段代码的输出:
function foo() { console.log('function foo') } var foo = 20; console.log(foo); // 20
console.log(foo); // function foo function foo() { console.log('function foo') } var foo = 20;
为什么两端代码输出不一样,不是都是先检查函数声明,再检查变量声明吗?
其实就是没有彻底理解,变量对象的创建与上下文生命周期的关系。
对于第一段代码,首先,创建全局上下文时创建变量对象,先将函数的声明放入变量对象中,再将变量的声明放入变量对象中;由于将变量放入时,遇到同名函数变量,所以跳过undefined的赋值;接着执行全局上下文中可执行的代码;执行到var foo =20 将20赋值给foo;然后执行console.log(foo)。结果当然是20。
而对于第二段代码,前面都是一样的,首先,创建全局上下文时创建变量对象,先将函数的声明放入变量对象中,再将变量的声明放入变量对象中;由于将变量放入时,遇到同名函数变量,所以跳过undefined的赋值;接着执行全局上下文中可执行的代码;执行第一句时就输出了function foo。
再来看个例子,加深一下印象:
console.log(testOne); // function testOne() { console.log('testOne') } function testOne() { console.log('testOne') } var testOne = 100; console.log(testOne); // 100
执行过程:
// 首先将所有函数声明放入变量对象中 function testOne() { console.log('testOne') } // 其次将所有变量声明放入变量对象中,但是因为testOne已经存在同名函数,因此此时会跳过undefined的赋值 // var testOne; // 然后开始执行阶段代码的执行 console.log(testOne); // function testOne() { console.log('testOne') } testOne= 100; console.log(testOne); // 100
二、活动对象(Active Object)
在未进入执行阶段之前,变量对象中的值都不能访问。但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能访问了,然后开始进行执行阶段的操作。
所以活动对象和变量对象其实都是同一个对象,只是他们处于执行上下文不同的生命周期。只有处于函数调用栈栈顶的执行上下文的变量对象,才可以变成活动对象。
三、变量提升
根据变量对象的创建过程,很容易解释变量提升。
从创建过程可以看出,function声明会比var声明优先级更高,且function声明时直接会将属性值设为在内存地址的引用,而变量声明时为undefined,或者会直接跳过。
看一个例子来理解变量提升:
function test() { console.log(a); console.log(foo()); var a = 1; function foo() { return 2; } } test();
直接从test()的执行上下文开始理解。全局作用域中运行test()时,test的执行上下文开始创建。用以下形式来表示:
// 创建过程 testEC = { // 变量对象 VO: {}, scopeChain: {} } // 因为本文暂时不详细解释作用域链,所以把变量对象专门提出来说明 // VO 为 Variable Object的缩写,即变量对象 VO = { arguments: {...}, //注:在浏览器的展示中,函数的参数可能并不是放在arguments对象中,这里为了方便理解,我做了这样的处理 foo: <foo reference> // 表示foo的地址引用 a: undefined }
之后开始执行:
// 执行阶段 VO -> AO // Active Object AO = { arguments: {...}, foo: <foo reference>, a: 1, this: Window }
因此,上面的例子其实变成了这样:
function test() { function foo() { return 2; } var a; console.log(a); console.log(foo()); a=1; } test();
注意,var bar = function(){ ....}也是属于var声明,变量对象创建时也会被赋值为undefined,或者跳过。
四、全局上下文的变量对象
在浏览器中,全局对象为window。全局上下文的变量对象,就是window对象。this也指向window。
此外,全局上下文的声明周期与程序的声明周期一样,只要程序运行不结束,比如关掉浏览器窗口,全局上下文就会一直存在。
其它所有的上下文环境,都能直接访问全局上下文的属性。