变量对象

一、变量对象的创建

之前提到执行上下文的生命周期,里面的所有过程都很重要,再复习一遍:

 

 

 首先是创建过程中的创建变量对象(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。

此外,全局上下文的声明周期与程序的声明周期一样,只要程序运行不结束,比如关掉浏览器窗口,全局上下文就会一直存在。

其它所有的上下文环境,都能直接访问全局上下文的属性。

posted @ 2019-08-29 21:42  二猫子  阅读(204)  评论(0编辑  收藏  举报