「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」
前言
每一个函数都有属于自己的执行上下文(execution context)而每个函数上下文中又包含了作用域(scope)。这些相关内容可以在昨天的文章JavaScript ES3中的执行上下文是怎样的 I 有详细的介绍。那么问题来了多个函数直接又是怎样关联到一起的呢?这就引出了一个新的概念作用域链(scope chain)。
作用域链(scope chain)
函数运行时为了能保证函数中的变量对象(variable object)能够进行有序访问。每当我们在使用一个变量时会先从当前上下文(context)中去查找,找到的话则停止没有的会沿着作用域链向上查找。如果都没找到的话才能全局上下文(globalContext)中去查找。
let n = 1
function fun1(){
function fun2(){
console.log(n)
}
fun2()
}
fun()
上述代码执行过程大致如下:
- 我们在使用变量n时,首先在函数fun2(functionContextfun2)中去查找
- 没找到则会沿着作用域链(scope chain)中去查找
- 沿着作用域链(scope chain)来到函数fun1(functionContextfun1)
- 还是没有找到,此时作用域()链(scope chain)已经到头了。
- 则来的全局作用域(globalContext)中查找,最终在全局作用域(globalContext)中找到了变量n结束。
作用域链的形成?
当我创建一个函数时,函数的内部属性[[scope]]会保存全局上下文的变量对象(variable object)以及如果有函数上下文(functionContext)的话还会保存的活动变量(VO)。
大致情况如下:
fun1.[[scope]] = [
globalContext.VO
]
fun2.[[scope]] = [
globalContext.VO,
functionContextfun1.AO
]
模拟执行过程
var a = 1
function fun1(b){
var c = 1
console.log(a,b,c,arguments)
}
fun1(1,2,3)
我们对上述代码模拟执行过程:
1.将全局作用域入栈到执行上下文栈中。
var ECStack = [globalContext];
- 创建函数fun1时,内部属性[[scope]]会保存全局上下文(globalContext)的变量对象(VO)。
var fun1Scope.[[scope]] = [
globalContext.VO
];
- 创建函数fun1的函数上下文(functionContextFun1),确认作用域链
var functionContextFun1 = {
Scope: fun1Scope.[[scope]],
}
- 创建函数上下文(functionContextFun1)的活动变量
var functionContextFun1 = {
Scope: fun1Scope.[[scope]],
AO:{
arguments: [],
c:undefined
}
}
- 将活动变量unshift到当前作用域(Scope)中
var functionContextFun1 = {
Scope: [AO,...fun1Scope.[[scope]]],
AO:{
arguments: [],
c:undefined
}
}
- 进入执行阶段,将创建好的functionContextFun1入栈
var functionContextFun1 = {
Scope: [AO,...fun1Scope.[[scope]]],
AO:{
arguments: [],
c:undefined
}
}
var ECStack = [ functionContextFun1, globalContext ];
- 执行函数fun1,并对活动变量进行赋值
var functionContextFun1 = {
Scope: [AO,...fun1Scope.[[scope]]],
AO:{
arguments: [1,2,3],
c:1
}
}
var ECStack = [ functionContextFun1, globalContext ];
- 当执行到console.log方法时,会对使用到的变量进行查找;在函数内部找到变量c,从形参中找到变量b,从全局作用域中找到c。执行完毕后将functionContextFun1进行出栈。
var ECStack = [globalContext];