JavaScript词法作用域和动态作用域,作用域链

86 阅读3分钟

作用域

aircraft-3007303__340.webp

作用域:程序源代码定义变量区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript采用词法作用域(lexical scoping),也就是静态作用域。

静态作用域与动态作用域

因为JavaScript采用的是词法作用域,函数的作用域在函数定义的时候就决定了。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
一个简单示例,思考下输出结果?

示例

  • 假设JavaScript采用静态作用域:执行foo函数,先查找内部是否有局部变量value,如果没有,则根据代码书写位置,查找上一层代码,也就是value=1,结果=1。
  • 假设JavaScript采用动态作用域:执行foo函数,查找内部是否有局部变量value,如果没有,就从调用函数的作用域,也就是bar函数内部查找,所以结果=2.

思考题

《JavaScript权威指南》中的例子

1

2 两段代码各自的执行结果是多少?
原文地址

作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文中查找,一直找到全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

下面,以函数创建和激活来说明作用域链的创建和变化

函数创建

之前有说道,函数的作用域在函数定义的时候就决定了。这是因为函数内部有一个属性[[scope]],是虚拟出来的一个属性,我们实际访问时访问不到这个属性的。当函数创建的时候,就会保存所有父级变量对象到其中,但是注意[[scope]]并不代表完整的作用域链!

比如我们创建这样一个函数

 
function foo() {
    function bar() {
        ...
    }
}

函数创建时,对应的[[scope]]


foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

思考题

思考

函数激活

结合变量对象和执行上下文,查看函数执行上下文中作用域链和变量对象的创建过程。

以此为例 eg

  • 1,checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
    globalContext.VO
];
  • 2,.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
    checkscopeContext,
    globalContext
];
  • 3,checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
    Scope: checkscope.[[scope]],
}
  • 4,第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
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
];

原文地址