我们已经知道,在调用一个函数时, 一个新的执行上下文将会被创建, 而一个执行上下文的周期分为两个阶段

  • 创建阶段: 在这个阶段,执行上下文将会分别创建变量对象, 建立作用域链, 确定this指向
  • 执行阶段: 创建完成后, 就会执行代码, 这时候会完成变量赋值,函数引用, 以及一些其他代码

image.png

从这里我们可以看到, 执行上下文时涉及到了变量对象, 作用域链, this指向等很多重要的概念. 这里我们主要理解变量对象

变量对象

变量对象的创建经历以下三个过程:

  • 建立argument对象. 检查当前上下文中的参数, 建立该对象的属性和属性值
  • 检查当前上下文的函数声明.也就是使用function关键字声明的函数.在变量对象中,以函数名作为一个属性, 属性值指向该函数所在内存地址的一个引用. 如果该函数名属性已经存在, 则该属性将会被新的引用所覆盖
  • 检查当前上下文的变量声明.每找到一个变量声明,就在变量对象中以变量名建立一个属性, 属性值为undefined.如果该变量名的属性已经存在,则会直接跳过,原属性值不会发生变化

image.png
根据这个规则,理解变量提升就变得更加简单, 可以使用变量对象的创建过程来解释变量提升

比如下面例子

console.log(foo) // function foo (){}
function foo (){}
var foo = 20
// 上述代码的执行顺序为
// 首先将所有函数声明放入变量对象中
function foo () {}
// 其次将所有变量声明放入变量对象中, 但是foo已经存在同名函数,则foo = undefined会跳过
// var foo = undefined 
console.log(foo)
foo = 20;

从上面的规则可以看出,function的声明会比var声明的优先级更高一些.
看下下面部分例子

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

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

test();

执行顺序

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

在上述例子中, 从test()调用时, test的执行上下文开始创建.

// 创建过程
testEC = {
    // 变量对象
    VO: {},
    // 作用域链
    scopeChain: {}
}
// 本文只讨论变量对象VO
VO = {
    argument: {...},
    foo: <foo的引用地址>,
    a: undefined
}

未进入执行阶段时, 变量对象中的属性都不可以被访问.但是进入执行阶段,变量对象转为了活动对象,里面的对象都可以访问了,然后开始进行执行阶段的操作

如果被问到变量对象和活动对象的区别, 其实他们都是一个对象,只是出于执行上下文的不同生命周期内.只有出于函数调用栈栈顶的执行上下文中的对象,才会变成活动对象.
// 执行阶段
VO = {
    argument: {...},
    foo: <foo的引用地址>,
    a: 1,
    this: window
}

全局上下文的对象

以浏览器为例, 全局对象为window
全局对象的变量对象就是window对象.this也是指向window

// 全局对象
windowEC = {
    VO: window,
    scopeChain: {},
    this: window
}

全局上下文的生命周期和程序的生命周期保持一致. 只要程序不停止运行, 比如关掉浏览器窗口, 全局上下文就会一直存在.


lolo
12 声望0 粉丝