深入理解作用域链
Opened this issue · 0 comments
18888628835 commented
当JavaScript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
每个执行上下文包含三个重要属性
- 变量对象
- 作用域链
- this
当查找变量时,首先从当前上下文中的变量对象查找,如果没有就会往上查找父级作用域中的变量对象,最后的终点是访问最外层上下文中的变量对象,如果没有就报错。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
当执行一段全局代码时,就会生成一个执行上下文,里面会包含全局变量对象
var a=123
globalContext.VO={
a:123
}
函数书写
当书写一段函数代码时,就会创建一个词法作用域,这个作用域是函数内部的属性,我们用[[scope]]表示,它里面保存父变量对象,所以[[scope]]就是一条层级链。
function fn(){
}
/*
fn.[[scope]]=[
globalContext.VO
]
*/
函数调用
当函数调用,就意味着函数被激活了,此时创建函数上下文,创建活动对象,然后将活动对象(AO)推到作用域链的前端。
我们用scope
来表示此时的作用域
fnContext={
Scope:[AO,fn.[[scope]]]
}
结合例子
我们来分析以下代码函数上下文中的变量对象和作用域的创建过程
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
1、全局上下文创建,生成全局变量对象VO,checkscope
函数创建,生成内部属性[[scope]],并且把父变量对象放进去。
checkscope.[[scope]]=[
globalContext.VO
]
2、函数调用了,创建函数上下文并压入执行栈
ECStack=[globalContext,checkscopeContext]
3、函数调用的分析阶段,做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext={
Scope:checkscope.[[scope]]
}
4、第二步:创建活动对象,初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
5、第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6、准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];