JS基础知识 —— 变量对象

接着执行上下文来总结,前面已经说了,执行上下文的生命周期分为创建阶段和执行阶段。而创建阶段是创建变量对象,建立作用域链和确定this的指向,这些对学习JS都很重要。

JS基础知识 —— 变量对象_第1张图片

这篇重点是在变量对象(Variable Object)


变量对象的创建,依次经历了以下的几个历程

  1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性和属性值。

  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在该变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果该函数名的属性已经存在,那么该属性将会被新的引用所覆盖(即使用新值替换旧值,也解释JS为什么没有函数重载)。

  3. 检查当前上下文中的变量声明(es6以前就是指使用 var 声明的变量),每找到一个变量声明,就在该变量对象中以变量名创建一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性不会被修改

JS基础知识 —— 变量对象_第2张图片

这下面有一段代码,先来看看。

function foo() {
    console.log('foo');
}
var foo = 20;
console.log(foo);// 20

不是说foo变量不会覆盖函数的吗?为什么最后还是输出了20呢?

!!!注意:上面的规则只是针对于变量对象的创建过程,而变量对象的创建只是执行上下文的创建阶段。而foo = 20执 行上下文的执行阶段中运行的,输出结果是 20 并不奇怪。

对比一下这段代码

console.log(foo); // ƒ foo() { console.log('foo'); }
function foo() {
    console.log('foo');
}
var foo = 20;

这里为什么是输出 ƒ foo() { console.log('foo'); } 呢?很简单,因为执行到 console.log(foo)的时候,此时foo的值还是保存着指向函数foo在内存地址的引用而已,所以打印了函数foo,而不是20。

// 执行顺序

// 首先将所有函数声明放入变量对象中
function foo() { console.log('foo') }

// 其次将所有变量声明放入变量对象中
// 但是因为foo已经存在同名函数,因此此时会跳过undefined的赋值
// var foo = undefined; 该次赋值无效

// 然后开始执行阶段代码的执行
console.log(foo); // function foo
foo = 20;


这也是变量提升的本质,从变量对象的创建过程中可以看出,function声明会比var声明优先级高。

// demo01
function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}
test();

我们从执行上下文开始理解。代码开始执行时, 全局上下文会入栈,然后遇到test()时就开始它的执行上下文的创建。

// 创建过程
testEC = {
    // 变量对象
    VO: {},
    // 作用域链
    scopeChain: {}
}

// VO 为 Variable Object的缩写,即变量对象
VO = {
    //注:在浏览器的展示中,函数的参数可能并不是放在arguments对象中,这里为了方便理解,做了这样的处理
    arguments: {...},  
    foo:   // 表示函数foo的地址引用
    a: undefined
}

!!!注意:未进入执行阶段之前,变量对象中的属性都不能访问!当进入执行上下文的执行阶段之后,变量对象转变为了活动对象,里面的属性才可以被访问,然后开始进行执行阶段的操作。

变量对象和活动对象有什么不同呢?答案是 变量对象和活动对象都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于函数调用栈栈顶的上下文中(即当前上下文)的变量对象,才会变成活动对象。

// 执行阶段
VO ->  AO // Active Object
AO = {
    arguments: {...},
    foo: ,
    a: 1,
    this: Window
}



再来看看下面的一个例子

// demo2
function test() {
    console.log(foo);
    console.log(bar);

    var foo = 'Hello';
    console.log(foo);
    var bar = function () {
        return 'world';
    }

    function foo() {
        return 'hello';
    }
}

test();
// 创建阶段
VO = {
    arguments: {...},
    foo: ,
    bar: undefined
}
// 这里有一个需要注意的地方,因为var声明的变量当遇到同名的属性时,会跳过而不会覆盖
// 执行阶段
VO -> AO
VO = {
    arguments: {...},
    foo: 'Hello',
    bar: ,
    this: Window
}


全局上下文的变量对象

以浏览器为例,全局对象为window。全局上下文有一个特殊的地方,它的变量对象就是window对象。全局上下文中的this,也很特殊,this也是指向window

// 以浏览器中为例,全局对象为window
// 全局上下文
windowEC = {
    VO: Window,
    scopeChain: {},
    this: Window
}

除了上面提到的,全局上下文的生命周期,与程序的生命周期一致。只有程序运行不结束,全局上下文就会一直存在。其它所有的上下文环境,都能直接访问全局上下文的属性。

你可能感兴趣的:(JS基础知识)