JavaScript系列之变量对象

JavaScript编程的时候总规避不了声明变量和函数,可是解释器是如何而且在什么地方去查找这些变量和函数呢?接下来,再延续上一篇《JavaScript系列之执行上下文和执行栈》,经过对变量对象(Variable Object)的介绍对执行上下文有一个更深一步的了解。javascript

上一篇文章也提到了,一个执行上下文的生命周期能够分为三个阶段:java

详细了解执行上下文对于初学者来讲极为重要,由于其中涉及到了变量对象,做用域链,this等不少JavaScript初学者没彻底搞懂,且极为重要的概念,它关系到咱们能不能真正理解JavaScript,真正理解也能更为轻松地胜任后续工做,在后面的文章中咱们会一一详细介绍,这里咱们先重点了解一下变量对象git

变量对象

变量对象(Variable Object)是一个与执行上下文相关的数据做用域,存储了在上下文中定义的变量函数声明,先来看一段代码示例:github

function foo(){
    var a = 10;     
    function b(){}
    (function c(){});
    console.log(a);     // 10
    console.log(b);   // function b(){}
    console.log(c);   // Uncaught ReferenceError: c is not defined
}

foo();
复制代码

在上面的例子中,foo()函数的变量对象包含变量a函数b()的声明。这里要注意的一点是,函数表达式并不像函数声明同样包含在变量对象中,在示例中所看到的那样,访问c()函数会致使引用错误。由于变量对象是抽象的和特殊的,它不能在代码中访问,但会由JavaScript引擎处理。express

上面利用的是函数上下文下的变量对象来讲明变量对象储存了什么,但变量对象还存在于全局上下文中,接下来就分别来聊聊全局上下文中和函数上下文中的变量对象吧。编程

全局上下文

以浏览器中为例,全局对象为window。 全局上下文有一个特殊的地方,它的变量对象,就是window全局对象。而这个特殊,在this指向上也一样适用,this也是指向window数组

// 以浏览器中为例,全局对象为window
// 全局上下文建立阶段
// VO 为变量对象(Variable Object)的缩写
windowEC = {
    VO: Window,
    scopeChain: {},
    this: Window
}
复制代码

除此以外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,好比关掉浏览器窗口,全局上下文就会一直存在。其余全部的上下文环境,都能直接访问全局上下文的属性。浏览器

函数上下文

在上面已经提到了,变量对象存储了执行上下文中的变量和函数声明,但在函数上下文中,还多了一个arguments(函数参数列表), 一个伪数组对象。函数

这时变量对象的建立阶段会包括:ui

  1. 建立arguments对象。检查当前上下文中的参数,创建该对象下的属性与属性值。
  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名创建一个属性,属性值为指向该函数所在内存地址的引用。若是变量对象已经存在相同名称的属性,则彻底替换这个属性。
  3. 检查当前上下文中的变量声明var 声明的变量),默认为 undefined;若是变量名称跟已经声明的形式参数或函数相同,为了防止同名的函数被修改成undefined,则会直接跳过变量声明,原属性值不会被修改。

对于第3点中的“跳过”一词想必你们会有一丝疑问?底下例子中既然按照上面的规则,变量声明的foo遇到函数声明的foo会跳过,但是为何最后foo的输出结果仍然是被覆盖了?

function foo() { console.log('I am function foo') }
var foo = 10;

console.log(foo); // 10
复制代码

理由其实很简单,由于上面的三条规则仅仅适用于变量对象的建立过程,也就是执行上下文的建立过程。而foo = 10是在执行上下文的执行过程中运行的,输出结果天然会是10。对比下例:

console.log(foo); // ƒ foo() { console.log('I am function foo') }
function foo() { console.log('I am function foo') }

var foo = 10;
console.log(foo); // 10
复制代码

为啥又是不同的结果呢?其实它的执行顺序为:

// 首先将全部函数声明放入变量对象中,函数声明变量提高
function foo() { console.log('I am function foo') }

// 其次将全部变量声明放入变量对象中,可是由于foo已经存在同名函数,所以此时会跳过变量声明默认undefined的赋值
// var foo = undefined;

// 而后开始执行阶段代码的执行
console.log(foo); // ƒ foo() { console.log('I am function foo') }

// 在执行上下文的执行过程当中运行
foo = 10;
console.log(foo); // 10
复制代码

根据上面的规则,理解变量提高就变得十分简单了,咱们也能够看出,function声明会比var声明优先级更高一点。为了帮助你们更好的理解变量对象,咱们再结合一个简单的例子来进行探讨。

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

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

test();

/* 结果为: undefined 2 */
复制代码

根据上述的规则,理解变量提高后能够将执行顺序理解为:

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

test();
复制代码

这样是否是一目了然了呢?

固然还须要注意的是,函数未进入执行阶段以前,变量对象中的属性都不能访问!可是进入执行阶段以后,变量对象(VO)转变为了活动对象(AO),而后开始进行执行阶段的操做。

执行阶段

当前进入执行阶段,变量对象(VO)激活成活动对象(AO),里面的属性都能被访问了,函数会顺序执行代码,改变变量对象的属性值,此阶段的执行上下文代码会分红两个阶段进行处理:

  1. 进入执行上下文
  2. 执行代码

进入执行上下文

当进入执行上下文时,这时候尚未执行代码。让咱们看一个例子:

function foo(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
  
foo(10); 
复制代码

当进入带有参数10的foo函数上下文时,AO表现为以下:

AO = {
    arguments: {
        0: 10,
        1: undefined,
        length: 1
    }
    a: 10,
    b: undefined,
    c: undefined,
    d: <function reference to d>, e: undefined, } 复制代码

x 是函数表达式,因此不在变量对象当中,e 变量引用的值也是函数表达式,因此变量 e 自己是声明,因此在变量对象当中。

执行代码

这个阶段会按顺序执行代码,修改变量对象的属性值,紧接上面的例子,执行完成后AO以下:

AO = {
    arguments: {
        0: 10,
        1: undefined,
        length: 1
    }
    a: 10,
    b: undefined,
    c: 10,
    d: <reference to function declaration d>,
    e: <reference to Function expression to _e>,
}
复制代码

到这里变量对象的建立过程就介绍完了,让咱们简短地总结一下:

  1. 全局上下文的变量对象初始化是全局对象
  2. 函数上下文的变量对象初始化只包括 Arguments 对象
  3. 在进入执行上下文时会依次给变量对象添加形参函数声明变量声明等初始的属性值
  4. 函数未进入执行阶段以前,变量对象中的属性都不能访问
  5. 在执行代码阶段,会再次修改变量对象的属性值,并赋予该有的属性值

若是以为文章对你有些许帮助,欢迎在个人GitHub博客点赞和关注,感激涕零!

相关文章
相关标签/搜索