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

引言

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


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


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

1、执行环境

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

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

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

javascript精雕细琢(三):作用域与作用域链_第1张图片
image

由上图我们清晰的看到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都不注释

javascript精雕细琢(三):作用域与作用域链_第2张图片
image


由上图可以看出,此时作用域链中存在 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

javascript精雕细琢(三):作用域与作用域链_第3张图片
image


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

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


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

javascript精雕细琢(三):作用域与作用域链_第4张图片
image


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

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



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


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

你可能感兴趣的:(javascript精雕细琢(三):作用域与作用域链)