定义 作用域链(Scope Chain)
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
在词法作用域时讲过,函数在词法阶段(函数定义时)会创建该函数的词法作用域,保存在自身函数的[[Scope]]属性中,且不能被改变。
[[Scope]]
会保存所有父变量对象到其中,就是所有父变量对象的层级链。
Scope = AO + [[Scope]] Scope = [AO].concat([[Scope]]) // 一个执行上下文的完整作用域
function fn() {
function foo() {
// ...
}
}
函数在创建时的作用域为:
fn.[[Scope]] = [
globalContext.VO
]
foo.[[Scope]] = [
fnContext.AO,
globalContext.VO
]
在前面说到变量对象在创建阶段还会建立作用域,那么就来分析一下建立作用域的过程。
在创建变量对象 VO
时,会建立属性 Scope
,其值是在词法阶段创建的词法作用域 [[Scope]]
。
这时会复制 [[Scope]]
对象到属性 Scope
中,如下
VO = {
arguments: {...},
Scope: [[Scope]]
}
又将活动对象 VO
添加到作用链的前端,Scope = [AO].concat([[Scope]])
得到如下
VO = {
arguments: {...},
Scope: [AO, [[Scope]] ]
}
最后再来看一个完整的示例
var a = 10
function fn() {
var b = 20
return b
}
fn()
执行过程如下
- 创建函数
fn
,同时创建词法作用域[[Scope]]
fn.[[Scope]] = {
globalContext.VO
}
- 执行函数
fn
,创建该函数的执行上下文的变量对象,并初始化arguments
和标识符
fnContext = {}
fnContext.VO = {
arguments: {
length: 0
},
b: undefined
}
- 建立该变量对象的作用域
fnContext = {
VO: arguments: {
length: 0
},
b: undefined,
Scope: fn.[[scope]]
}
- 将活动对象压入
fn
作用域链顶端
fnContext = {
VO: arguments: {
length: 0
},
b: undefined,
Scope: [ VO, [[scope]] ]
}
- 开始执行
fn
函数,修改AO
的值
fnContext = {
VO: arguments: {
length: 0
},
b: 20,
Scope: [ VO, [[scope]] ]
}
- 执行完
fn
函数,函数上下文fnContext
从 ESC 推出。
ESC = [
globalContext
]