javascript之作用域与作用域链

前言

作用域是指程序中能定义变量或者函数的范围,可以理解为地盘,是一个抽象的概念。
作用域分为静态作用域和动态作用域,在JavaScript中采用的是静态作用域,也称词法作用域。

正文

静态作用域

静态作用域是指声明的作用域在程序被定义的时候就确定的,在JavaScript中,当函数定义的时候,函数作用域就被创建了,此时的作用域就是静态作用域。

看如下代码:

    'use strict';
    
    var a = 1;
    
    function fn1() {
        console.log(a);
    };
    
    function fn2() {
        var a = 2;
        fn1(); // 1
    };
    
    fn2();
复制代码

当函数fn1被调用的时候,fn1函数内部会去找变量a,先从函数内部开始找,如果没找到,则从定义fn1函数的上一层作用域找,结果找到了a,值为1。所以上面的代码运行结果是会在控制台打印1。

作用域链

首先需要知道几个概念:

每个函数内部都有一个[[scope]]的属性(这个属性仅供js引擎使用),当函数被创建的时候,[[scope]]属性会保存创建该函数的作用域中的所有对象(Variable Obejct)。

每个函数被调用的时候,会产生一个执行上下文的内部对象,这个对象里面包含了:

  • 作用域链([[scope]])
  • 活动对象(Active Object)
  • this

说明: 执行上下文在函数执行完毕之后,会自动销毁,他里面的作用域链是复制的函数内部的属性[[scope]]。

通过下面代码的执行过程,来深入理解作用域链:

    'use strict';
    
    var a = 1;
    function fn1(x) {
        var b = 2;
        return (a + b + x);
    };
    fn1(1);
复制代码

执行过程如下:
1、创建全局执行上下文环境(Global Execution context,下面简称globalEC),全局执行上下文压入执行上下文栈(Execution context stack,下面简称ECStack)。

    // 伪代码
    
    //创建全局执行上下文环境
    globalEC = {}
    
    //全局执行上下文压入执行栈中
    ECStack = [
        globalEC
    ]
复制代码

2、全局执行上下文环境初始化,即预解析阶段,会进行函数提升与变量提升

    // 伪代码
    
    //全局上下文环境初始化,GO代表的是全局作用域中保存的变量
    globalEC = {
        this: [global],
        GO: {
            [global]: [global],
            this: [global],
            a: undefined,
            fn1: function() {//...}
        }
    }
    
    // 函数fn1被创建时,fn1内部的[[scope]]被赋值
    fn1.[[scope]] = [globalEC.GO]
复制代码

3、进入执行阶段,代码按预解析后的顺序执行,并给变量赋值

    // 伪代码,边执行边给变量赋值
    globalEC = {
        this: [global],
        GO: {
            [global]: [global],
            this: [global],
            a: 1,
            fn1: function() {//...}
        }
    }
    
复制代码

4、当执行fn1()时,创建fn1的执行上下文(fn1 Execution context,下面简称fn1EC),fn1函数的执行上下文压入栈中,处于活动状态

    // 伪代码
    
    //创建fn1的执行上下文
    fn1EC = {}
    
    //fn1函数的执行上下文压栈
    ECStack = [
        fn1Ec,
        globalEC
    ]
复制代码

5、fn1函数进入预编译阶段,产生活动对象AO,活动对象AO被保存到作用域链的顶端,

    // 伪代码
    
    //fn1函数进入预编译阶段,产生活动对象(Active object,下面简称AO)
    fn1EC = {
        this: undefined, // 在严格模式下为undefined,非严格模式下为全局对象
        AO: {
            arguments: {
                0: undefined,
                length: 1
            },
            b: undefined
        },
        [[scope]]: [fn1EC.AO, globalEC.GO]
    }
复制代码

6、当fn1函数进入执行阶段

    // 伪代码
    fn1EC = {
        this: undefined, // 在严格模式下为undefined,非严格模式下为全局对象
        AO: {
            arguments: {
                0: 1,
                length: 1
            },
            b: 2
        },
        [[scope]]: [fn1EC.AO, globalEC.GO]
    }
复制代码

7、fn1函数内部引用了变量a, 但是内部并没有定义,此时就会沿着从 AO -> GO 这条作用域链去找,于是在全局作用域 GO 中找到了a的值。如果一直到全局作用域没有找到,那么此时就会报错了。
8、当fn1函数执行完毕,fn1的执行上下文出栈,并销毁,此时回到全局执行上下文环境。

    // 伪代码,fn1函数的执行上下文出栈,此时回到全局执行上下文环境
    ECStack = [
        globalEC
    ]
复制代码

说明:fn1函数中this的值,会因不同的执行上下文环境而变化。

以上就是上面代码的执行过程,代码无任何意义,只为理解执行上下文的作用域链。

总结

  • JavaScript中的函数的作用域是在函数定义的时候就已经创建了,属于静态作用域,也叫词法作用域。
  • 同一个作用域下,不同的调用会产生不同的执行上下文。
  • 作用域只是一个抽象的概念,具体变量的赋值是在代码的执行阶段,在执行上下文中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值