在《深入理解JavaScript执行上下文栈》这篇文章中,我们已经介绍了执行上下文相关概念:
分类:全局上下文、函数上下文
全局上下文:执行全局代码时,创建全局上下文。
函数上下文:执行函数时,创建函数上下文。
主要属性:
这一章节我们主要针对执行上下文中的作用域链进行讲解
概念:当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
因为JavaScript采用的是词法作用域(静态作用域),函数的作用域在函数定义的时候就决定了。
这是因为函数有一个内部属性[[scope]],当函数创建的时候,就会保存所有父变量对象到其中,我们可以理解[[scope]]就是所有父变量对象的层级链,但是要注意的是:[[scope]]并不代表完整的作用域链。
举个例子:
function outer() {
function inner() {
...
}
}
函数创建时,各自的[[scope]]为:
outer.[[scope]] = [
globalContext.VO
]
inner.[[scope]] = [
fooContext.AO,
globalContext.VO
]
当函数执行时,创建函数上下文,在这个过程中会复制函数[[scope]]属性中的变量对象创建作用域链。创建完活动对象后,就会将 AO添加到作用域链的顶端。
我们将执行上下文的作用域链命名为Scope:
Scope = [[Scope]].concat([AO])
至此,作用域链创建完毕。
结合之前讲的变量对象与执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:
function fn() {
var text = 'scope'
}
fn()
执行过程如下:
1.fn函数被创建,保存父级作用域链到内部属性[[scope]]
fn.[[scope]] = [
globalContext.Scope
]
2.执行fn函数,创建fn函数执行上下文,fn函数执行上下文被压入执行上下文栈
ECStack = [
fnContext,
globalContext
]
3.fn函数并不立即执行,开始进行解析,第一步:复制函数[[scope]]属性创建作用域链
fnContext = {
Scope: [fn.[[scope]]]
}
4.第二步:用arguments创建活动对象,随后初始化活动对象,加入形参、函数声明与变量声明
fnContext = {
AO: {
arguments: {
length: 0
},
text: undefined
},
Scope: [[[Scope]]]
}
5.第三步:将活动对象压入fn作用域链顶端
fnContext = {
AO: {
arguments: {
length: 0
},
text: undefined
},
Scope: [AO, [[Scope]]]
}
6.第六步:解析完成,开始执行函数,在函数执行的过程中修改AO的属性值
fnContext = {
AO: {
arguments: {
length: 0
},
text: 'scope'
},
Scope: [AO, [[Scope]]]
}
7.函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
]