18888628835/Blog

深入理解作用域链

Opened this issue · 0 comments

当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
];