从作用域到闭包

作用域

作用域就是程序代码中定义变量的区域。
JS采用了词法作用域,即静态的作用域。

JS代码是逐段来分析执行的,所以有上下文(context)的概念。

执行上下文

可执行代码有三种:

  • 全局代码
  • 函数代码
  • eval代码

当JS代码执行一段可执行代码时,会创建相对应的执行上下文(也可译作执行环境),
因此也就有:全局上下文、函数上下文...
它们都是在执行期被创建的,所以也可称作执行期上下文。

执行上下文有三个重要属性:

  • 变量对象(VO)
  • 作用域链
  • this对象

JS是通过执行上下文栈来管理执行上下文的。

变量对象(VO)
变量对象存储了在上下文中定义的变量和函数声明。

全局环境的变量对象始终存在,函数的变量对象只在函数的执行过程中存在
-《红书》

  • 在全局上下文中,变量对象就是全局对象;
  • 在函数上下文中,变量对象是活动对象(AO),它在进入函数上下文时创建;其中活动对象的 arguments 属性被初始化为函数的 arguments 对象;

作用域链(Scope Chain)
作用域链就是由多个执行上下文的变量对象构成的链表。

函数上下文的作用域链是借助函数内部的 [[Scope]] 属性生成的。
当一个函数被创建时,内部的 [[Scope]] 属性会保存所有父变量对象的层级链,什么意思?
让我们举个例子:

function foo() {
    function bar() {
        ...
    }
}

当上述代码的foo函数被创建时,它的 [[Scope]] 就保存了全局上下文的变量对象;
只有当我们执行foo()函数时,bar函数才被创建,这时它的 [[Scope]] 就保存了两个变量对象,分别是foo函数的活动对象 和 全局上下文的变量对象;也就是说,函数在创建时只保存了父变量对象。

当函数被执行时,JS就创建相应的函数上下文以及相对应的AO,只要在 [[Scope]] 的基础上把这个AO推入到前端,就成为了作用域链。比如上述bar()函数被执行时,它的作用域链就包含了:

  • 0 全局上下文VO (来自 [[Scope]] )
  • 1 foo函数AO (来自 [[Scope]] )
  • 2 bar自己的AO (创建时被推入前端)

我在这一块的理解过程中遇到了点曲折,既然AO、VO都是在执行时才有的,那上述的bar函数在创建时,foo函数的AO是哪里来的呢?突然意识到只有当foo执行时才会有bar函数的创建,就理清楚了。

上面提到的 [[Scope]] 是一个只有JS引擎才能访问的函数内部属性,它存在的目的就是被复制,然后构建出作用域链。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。