深入理解JavaScript系列(4):作用域链

《深入理解 JavaScript 系列(2):执行上下文栈》中提到,当 JavaScript 代码执行代码时,就会创建一个执行上下文。

对于执行上下文,都有三个重要属性:

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

我们来介绍一下作用域链。

作用域链

在查找变量的时候,会先从当前上下文的变量对象中查找,如果没找到,就会从上一级的执行上下文的变量对象中查找,直到找到全局对象为止。

而这样由多个执行上下文的变量对象构成的链表就时作用域链。

函数创建

每一个函数都有自己的作用域,内部有一个 [[scope]] 属性,当函数创建时,就会保存所有父级变量对象到其中,简单来说,[[scope]] 就是所有父级变量对象的层级链。
注意:[[scope]] 并不能代表完整的作用域链。

我们来看一个例子吧

1
2
3
4
5
function fn1() {
function fn2() {
function fn3() {}
}
}

函数创建时,我们来看看每一个函数的 [[scope]] 为:

1
2
3
4
5
6
7
8
9
10
11
12
fn1.[[scope]] = [
globalContext.VO
];
fn2.[[scope]] = [
fn1Context.AO,
globalContext.VO
];
fn3.[[scope]] = [
fn2Context.AO,
fn1Context.AO,
globalContext.VO
]

函数激活

当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用域链的前端。

在执行上下文中,我们将作用域链命名为 Scope

1
Scope = [AO].concat([[Scope]]);

我们来举个例子吧,对《JavaScript 权威指南》 中的例子进行修改

1
2
3
4
5
6
var name = 'hzq';
function checkname() {
var name = 'hzzzzzzzq';
return name;
}
checkname();

来看看执行过程吧

  • 创建 checkname 函数
1
2
3
checkname.[[scope]] = [
globalContext.VO
]
  • 创建 checkname 函数执行上下文,并将 checkname 函数执行上下文压入执行上下文栈
1
ECStack = [checknameContext, globalContext];
1
2
3
4
5
6
7
8
9
checknameContext = {
AO: {
arguments: {
length: 0
},
name: undefined
},
Scope: checkname.[[scope]]
}
  • 将活动对象压入 checkname 作用域链顶端
1
2
3
4
5
6
7
8
9
checknameContext = {
AO: {
arguments: {
length: 0,
},
name: undefined,
},
Scope: [AO, [[Scope]]],
};
  • 开始执行函数
1
2
3
4
5
6
7
8
9
checknameContext = {
AO: {
arguments: {
length: 0,
},
name: 'hzzzzzzzq',
},
Scope: [AO, [[Scope]]],
};
  • 返回 name,在一开始就找到了,所有直接返回该值,函数执行完毕。函数上下文弹出执行上下文栈。
1
ECStack = [globalContext];
-------------------- 本文结束 感谢阅读 --------------------