ES 基础 —— 作用域链
在介绍 Execution Context 时,提到了 Scope Chain。这里来详细说说它。
什么是 Scope Chain?
Scope Chain 由当前 Execution Context 的 VO 与其所有父级 Execution Context 的词法(Lexical)范畴上的 VO 组成。
用一段代码来解释:
function one() {
two()
function two() {
three()
function three() {
console.log('I am at function three');
}
}
}
one()
在 Global Execution Context 中调用 one()
,one()
调用 two()
,two()
调用 three()
。
调用函数 | Scope Chain |
---|---|
one() |
[one() VO] + [Global VO] |
two() |
[two() VO] + [one() VO] + [Global VO] |
three() |
[three() VO] + [two() VO] + [one() VO] + [Global VO] |
Scope Chain 与闭包
ES 中,闭包常常被当作只有高级开发者才能真正掌握的技术,但理解 Scope Chain 后,很容易就能理解闭包。就像 Crockford 说的:
An inner function always has access to the vars and parameters of its outer function, even after the outer function has returned…
举个闭包的例子:
function foo() {
const a = 'private variable'
return function bar() {
console.log(a)
}
}
const log = foo()
log() // private variable
上面的代码中,Global Execution Context 的 VO 中有一个函数 foo()
和一个变量 log
。这个闭包的奇妙之处在于那个 a
,即使 foo()
执行完毕了,a
还是能被访问到。 为什么?
// Global Execution Context when evaluated
global.VO = {
foo: pointer to foo(),
log: returned value of global.VO.foo,
scopeChain: [global.VO]
}
// Foo Execution Context when evaluated
foo.VO = {
bar: pointer to bar(),
a: 'private variable',
scopeChain: [foo.VO, global.VO]
}
// Bar Execution Context when evaluated
bar.VO = {
scopeChain: [bar.VO, foo.VO, global.VO]
}
可以从以上伪代码中看到,在调用 bar()
时,Scope Chain 为 [bar.VO, foo.VO, global.VO]
,访问 a
时,函数会沿着作用域链搜索 a
,a
就在 foo.VO
中。所以就访问到了。
Scope Chain 与 Prototype Chain
当要访问一个对象的某个属性时,解释器:
- 首先,通过 Scope Chain 定位对象。
- 然后,通过对象的 Prototype Chain 检索属性。
看个例子:
const bar = {}
function foo() {
bar.a = 'Set from foo()'
return function inner() {
console.log(bar.a)
}
}
foo()() // 'Set from foo()'
foo()()
执行时,解释器先通过 Scope Chain 找到了对象 bar
,然后在对象中找到了属性 a
。
const bar = {}
function foo() {
Object.prototype.a = 'Set from prototype'
return function inner() {
console.log(bar.a)
}
}
foo()() // 'Set from prototype()'
foo()()
执行时,解释器先通过 Scope Chain 找到了对象 bar
,但对象bar
中不存在属性 a
,于是,解释器查找 Prototype Chain,在 Object.prototype
中找到了属性 a
。