• javascript精雕细琢(三):作用域与作用域链


    引言

           作用域与作用域链是JS应用中无时无刻不在影响程序运行的关键属性,但是由于它的不可见性,或者说它存在的过于普遍,简直就像空气一样。所以对它的谈及,都很简单,而理解起来也不复杂。


           但是由于它的重要性,对它做一个篇幅的说明,也是一件理所应当的事情。而且它其实也并没有理解上那么简单。


           本文对作用域及作用域链的说明,可能并不全面。笔者尽量以自身开发过程中的理解,做到表述全面。

    1、执行环境

           按照《JavaScript高级程序设计》第3版中的定义——执行环境定义了变量或函数有权访问其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

           对于环境的简单理解,可以理解为{}一个大括号就是一个执行环境。如{{}},就是包含关系的两个执行环境。而由于所有的函数和变量,最终都是通过最外围有对应的调用,才能最终实现运行,所以将最外围的执行环境定义为全局执行环境,在宿主为web浏览器时,全局执行环境就为window对象

           每个函数都有自己的执行环境。当执行程序进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将环境弹出,把控制权返回给之前的执行环境。之后,该环境因为执行完毕,随即被销毁。但有一个例外,全局环境将永远不会被销毁,直到程序被关闭,如关闭浏览器。


    由上图我们清晰的看到JavaScript的两个阶段的执行流程。

    2、作用域与作用域链

           作用域链和作用域是一个包含关系。当代码在一个环境中执行时,会创建变量对象的一个作用域链。而作用域链中,包含的就是一个个有序排列的作用域。作用域链的作用,就是保证执行环境中的变量和函数的有序访问

           每次的变量或函数声明,都会形成一个标识符(即变量名或函数名)。 作用域链中的向上查找,实际上就是标识符的查找。找到匹配的标识符,就调用它的值。

           作用域链中包含的每个作用域对象,其中都包含着它自身环境中可访问的变量或函数(script作用域中,为全部的变量和函数;函数作用域中,子函数调用父函数环境中的变量或函数会形成闭包,那么可访问变量就为闭包调用的变量)。换一种角度理解的话,这些环境中可访问的变量或函数就是标识符,是当前作用域中可通过作用域链向上查找的标识符。

           一大段的文字不光读起来枯燥,理解起来也困难。所以,stop talking,show code。


    以函数为例

    function test() {
        let a = 1; //注释此行,保留其他a,c()打印2,并且作用域链失去闭包对象
        let b = 0;
        
        return () => { //形成闭包环境
            console.log(a);
            }
        }
    
        let a = 2; //注释此行及test中的a,c()打印3
        window.a = 3;  //注释全部a,报错
    
        const c = test();
        c();
    
        console.dir(test);
        console.dir(c);
            
    

    以上代码,通过**3次注释**以及**chrome浏览器打印**,可以清晰的看到作用域与作用域链的形成及作用域链**按顺序查找**的特性

    1)首先是闭包调用——所有a都不注释



    由上图可以看出,此时作用域链中存在3个作用域对象。 按索引顺序为0—Closure闭包环境、1—Script标签环境、2—Global全局环境;

    索引顺序就是标识符的查找顺序,所以当执行函数c()时,先在Closure闭包环境中查找,然后是Script标签作用域,最后是Global全局作用域。因为形成了闭包,那么标识符的查找肯定能在闭包环境中找到对应结果,所以打印1;

    值得注意的一点是,Closure与Script作用域对象中,存储的标识符的不同。closure只存储调用值a = 1,并没有存储b = 0。而Script中将环境中的所有标识符都存在了对象中;


    2)然后是取消闭包调用,改为Script标签作用域查找——注释test中的a = 1



    由上图可以看出,此时作用域链中的Closure闭包作用域消失,只剩下2个对象0—Script标签环境、2—Global全局环境;

    那么同样按索引顺序查找,找到了Script标签环境中的a = 2,没有继续寻找Global全局环境中的window.a = 3,最终打印2;


    3)最后是Global全局作用域查找——注释test中的a = 1及script中的a = 2



    与2)中情况对比,作用域链中的对象没有改变。但是Script对象中的标识符,随着a = 2被注释而消失

    按索引顺序查找,找到了Script标签环境中没有a,继续寻找Global全局环境,最终找到window.a = 3,打印3;



    函数test的打印,从始至终都是Script标签作用域及Global全局作用域。理解了c函数的作用域链,自然就明白了函数test的,所以不再展开赘述。


    如有错误或阐述不充分之处,欢迎指正~

  • 相关阅读:
    强制类型转换
    《thinking in java》 接口与内部类
    JAVA强制类型转换(转载+自己的感想)
    Java 面试题问与答:编译时与运行时
    Java注解处理器
    Java反射详解
    BZOJ5072:[Lydsy1710月赛]小A的树(树形DP)
    BZOJ4987:Tree(树形DP)
    BZOJ3791:作业(DP)
    BZOJ1972:[SDOI2010]猪国杀(模拟)
  • 原文地址:https://www.cnblogs.com/keepStudying/p/9870409.html
Copyright © 2020-2023  润新知