切换主题
作用域链
在学习什么是作用域链前,需先了解什么是作用域?
作用域
那么什么是作用域呢?有何作用?
作用域是指程序源代码中定义变量的区域,其规定了如何查找变量,确定当前执行代码对变量的访问权限。
作用域类型:
- 词法作用域:函数的作用域在函数
定义
的时候决定。 - 动态作用域:函数的作用域在函数
调用
的时候决定。
JavaScript就是采用的词法作用域,为了方便理解举个例子:
JavaScript
const who = 'yui'
function kon() {
console.log(who)
}
function mio() {
const who = 'mio'
kon()
}
mio() // 'yui'
代码执行结果输出'yui'
,而非'mio'
,完全符合词法作用域特点;若是动态作用域,此处的输出结果应该是'mio'
。
作用域链
什么是作用域链呢?
当查找变量的时候,会先从当前上下文的变量对象中查找,若未找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
由于JavaScript是词法作用域,函数的作用域在函数定义的时候就已经被决定。 在函数创建的时候,其内部属性[[scope]]
会将所有父级变量对象保存至其中,可以将[[scope]]
理解为所有父级变量对象的层级链(并非完整的作用域链)!
作用域链是如何创建和变化的呢?让我们从函数的创建和激活切入:
创建
比如上例中,函数kon
和mio
各自的[[scope]]
为:
JavaScript
mio.[[scope]] = [
globalContext.VO
]
kon.[[scope]] = [
mioContext.AO, // 函数用 AO 表示 VO
globalContext.VO
]
激活
当函数被激活时,进入其上下文,创建VO
/AO
后,就会将活动对象添加到作用链的前端,此时的作用域链ScopeChain
为:
JavaScript
ScopeChain = [AO].concat([[scope]])
作用域链ScopeChain
创建完毕。
总结
JavaScript
function kon(name) {
const fav = '吉太'
return name + ' fav: ' + fav
}
kon('yui')
总结一下函数执行上下文中作用域链和变量对象的创建过程:
- 函数
kon
被创建并将作用域链保存至[[scope]]
JavaScript
kon.[[scope]] = [
globalContext.VO
]
- 执行
kon
函数时,创建kon
函数的执行上下文,并将kon
函数的执行上下文压入ECS
JavaScript
ECS = [
konContext,
globalContext
]
kon
函数并不会立即执行,开始做准备工作:
3.1. 复制函数[[scope]]
属性创建作用域链JavaScriptkonContext = { Scope: kon.[[scope]] }
3.2. 用
arguments
创建AO
,随后初始化AO
(加入形参、函数声明、变量声明)JavaScriptkonContext = { AO:{ arguments:{ 0: 'yui', length: 1 }, fav: undefined }, Scope: kon.[[scope]] }
3.3. 将
AO
压入kon
作用域链顶端JavaScriptkonContext = { AO:{ arguments:{ 0: 'yui', length: 1 }, fav: undefined }, Scope: [AO, [[scope]]] }
准备工作完毕,开始执行函数,随着函数的执行,修改
AO
的属性值:
JavaScript
konContext = {
AO:{
arguments:{
0: 'yui',
length: 1
},
fav: '吉太'
},
Scope: [AO, [[scope]]]
}
- 找到
fav
的值为'吉太'
,返回后函数执行完毕,函数上下文从执行上下文栈中弹出。
JavaScript
ECS = [
globalContext
]