作用域
作用域就是程序代码中定义变量的区域。
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引擎才能访问的函数内部属性,它存在的目的就是被复制,然后构建出作用域链。