【深入JavaScript日记三】作用域链

前言

在上一节,我们深入学习了一下当js代码执行了一段可执行代码时,就会创建对应的上下文。
对于每个上下文,都有:变量对象(Variable object,VO),作用域链(Scope chain)
this这三个属性。

在上一节我们详细研究了变量对象
结果
这一次我们着重看看作用域链


正文内容

作用域

先科普一下作用域

通俗理解,作用域指的就是变量名能够起作用的区域(也就是说你声明的变量可以在哪些区域进行使用)。javascript中分为两种作用域:全局作用域,局部作用域。


作用域链

在《JavaScript深入之变量对象》中讲到,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

结合我们之前学到的东西,这玩意就可以理解为,当执行代码时,要用到某个变量时先从当前上下文里找,然后从他爹的上下文那里去找,然后去爷爷的上下文那去找…这个过程形成的链状结构就是作用域链。
下面,让我们以一个函数的创建时期来讲解作用域链是如何创建和变化的。

函数创建

在前面我们知道了,函数的作用域在函数定义的时候就决定了。
这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
函数内部的[[scope]]属性是虚拟出来的一个属性,我们实际访问时访问不到这个属性,这个属性是为了让我们更好的理解函数
但是我们可以通过每个函数的arguments属性侧面验证一下:

    function A() {
        var a = "函数A"
        console.log(arguments)
        B();

        function B() {
            var b = "函数B"
            console.log(arguments)
        }
    }
    A();

在这里插入图片描述
举个例子!!

function A() {
    function B() {
        ...
    }
}

函数创建时,各自的[[scope]]为:

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

B.[[scope]] = [
    AContext.AO,
    globalContext.VO
];

到这里我们顺一下思路,结合着之前的变量对象和执行上下文栈,总结一下函数执行上下文中作用域链和变量对象的创建过程

/**
* 这里我们要在函数里使用local_v ,相当于通过执行原理来找变量local_v 
*/
    var global_v= "全局变量";
    function checkscope(){
        var local_v = '局部变量';
        return local_v ;
    }
    checkscope();
  1. 保存父级作用域链】checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
    globalContext.VO
];
  1. 创建函数上下文并压栈】执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
    checkscopeContext,
    globalContext
];
  1. 开始函数准备工作】checkscope 函数并不立刻执行,开始做准备工作。
    第一步:【将函数的作用域链复制到函数上下文的Scope里】复制函数[[scope]]属性创建作用域链
checkscopeContext = {
    Scope: checkscope.[[scope]],
}
  1. 第二步:【在函数上下文里创建活动对象】用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        local_v: undefined
    }
}
  1. 第三步:【将活动对象压入本函数作用域链顶端】将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        local_v: undefined
    },
    Scope: [AO, [[Scope]]]
}

  1. 执行函数并修改值】准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        local_v: '局部变量'
    },
    Scope: [AO, [[Scope]]]
}
  1. 结束之后函数上下文弹栈】查找到 local_v 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
    globalContext
];

最后

到这里还是要再总结一下:作用域链与原型链的区别:

当访问一个变量时,解释器会先在当前作用域查找标识符,如果没有找到就去父作用域找,作用域链顶端是全局对象window,如果window都没有这个变量则报错。

当在对象上访问某属性时,首选会查找当前对象,如果没有就顺着原型链往上找,原型链顶端是null,如果全程都没找到则返一个undefined,而不是报错。

通过这一部分的学习,还是感受收获颇丰,也愈发的激起了对于js的兴趣,js作为一个弱语言,本身的编程自由程度是非常高的,用法都很灵活,想要进行高效的开发,熟悉这些原理是很关键的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AntyRia

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值