嗑嗑瓜子,聊聊作用域、作用域链、变量提升、预编译和闭包(中)

125 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

作用域、作用域链、变量提升、预编译和闭包

2. 预编译

在了解预编译之前首先了解一下AO对象和GO对象

  • AO对象(Activation Object),在函数预编译时创建,函数执行上下文对象
  • GO对象(Global Object),在全局预编译时创建,全局执行上下文对象

接下来,我们来了解预编译到底会干些什么

预编译发生在函数执行的前一刻(四部曲)

  1. 创建AO对象(Activation Object)
  2. 找形参和变量声明,将变量声明和形参作为AO的属性名,值为undefined
  3. 将实参和形参值统一
  4. 在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体

以下面代码举个例子:

function test(a,b){
    console.log(a)
    c = 0
    var c;
    a = 3
    b = 2
    console.log(b)
    function b(){}
    function d(){}
    console.log(b)
}
test(1)

让我们来分析一下这段代码:

//首先创建一个AO对象
AO:{
     //然后找形参和变量声明,将变量声明和形参作为AO的属性名,值为undefined
     a:undefined,
     b:undefined,
     c:undefined
     //接着将实参和形参值统一
     a:1,
     b:undefined,
     c:undefined,
     //最后在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体
     a:1,
     b:function b() {},
     c:undefined,
     d:function d() {}
     
}

至此,预编译的四部曲结束,预编译完成,接下来是开始调用,函数执行 第一行执行console.log(a),此时经过预编译后,a的值是1,所以打印出来应该是1 接着c被赋值成0,var c会被变量提升,提到前面去声明,a被赋值成3,b被赋值成2,所以此时的AO对象变成了

 AO:{
     a: 3,
     b: 2,
     c: 0,
     d: function d() {}
 } 

所以接下来执行console.log(b),打印的结果就是2, 紧接着,b和d的函数声明在预编译的时候就已经编译过了(先执行),b的值被改成了2(后执行),所以值为2(前者被后者覆盖),而d没有变, 所以最后一行console.log(b)打印结果还是2,上截图

image.png

预编译也发生在全局(三部曲)

  1. 创建GO对象
  2. 找形参和变量声明,将变量声明和形参作为GO的属性名,值为undefined
  3. 在全局里找函数声明,将函数名作为GO对象的属性名,值赋予函数体

与函数的预编译相比,它少了将实参形参统一的步骤,除此之外基本与函数预编译一致,可以参考函数预编译的例子,这里就不重复解释了

作用域链

在了解作用域之前,要先了解一下这三个定义:

  • 执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个之I型那个上下文,当函数执行完毕,它所产生的执行上下文会被销毁
  • [[scope]]:作用域属性,也称为隐式属性,仅支持引擎自己访问。函数作用域,是不可访问的,其中存储了执行期上下文的集合。
  • 查找方式:当根据作用域链查找变量的时候,是从作用域链的顶端一次向下查找的。

在 **[[scoped]]** 作用域属性中存储的执行器的上下文对象的集合,这个集合呈链式链接,我们把它称为作用域链

我们结合实例去理解作用域链:

function a(){

    function b(){

    }
    b()
}
a()

这是一个很简单的函数嵌套,每个函数都有他们自己的 [[scoped]] 属性,他们里面的作用域链都是不同的,首先看a的,

image.png

a的里面只有a的AO对象和全局的GO对象,接着是b的

image.png

b的里面有b的AO对象,a的AO对象和全局的GO对象, 综上可以知道,每个函数的作用域链其实是不一样的,他是从自身开始一层一层向外,越外层,排在越后面

在知道了作用域链了之后,我们对其查找方式也可以进一步详细说明,前文说过查找变量的方式,当根据作用域链查找变量的时候,是从作用域链的顶端一次向下查找变量的。根据上面的图,我们知道,我们从哪个函数里面找变量,该函数是排在最顶端的,然后顺着作用域链去找,比如从b函数里面找变量,先从b的AO开始,如果找不到就进入a的AO,如果再找不到,就进入GO里面找。

这就是作用域链,它是一个链式集合。