美文网首页JS相关让前端飞Web前端之路
js深度剖析 : 执行环境和作用域链

js深度剖析 : 执行环境和作用域链

作者: 小貔 | 来源:发表于2017-09-02 17:10 被阅读84次

    执行上下文

    execution context 又称执行上下文或者执行环境.

    执行上下文是JavaScript中一个极为重要的概念. 我们可以简单的理解执行上下文就是我们代码执行时所在的环境. 这个环境中包含着许多对我们来说十分重要的数据. 例如作用域链 , 数据的访问权,执行代码等等 .

    当我们的代码在JavaScript中运行时,便会进入不同的执行上下文.

    其中Global execution context是最外围层的一个执行环境 , 即JavaScript代码开始运行的默认环境.也就是我们常说的全局环境. 这个执行上下文是由浏览器创建并初始化的 .

    除此之外,每个函数都有自己的一个执行上下文. 当我们进行函数调用时, 其执行上下文便会创建. 这也是我们所认为的局部环境( local context) .

    variable object
    • global object

    对于全局环境,浏览器中用window对象来作为它的variable object . 该全局对象中存储着JavaScript所定义的全部内置对象和全局函数,以及宿主环境的宿主对象和我们在全局环境中定义的函数和变量等.

    这也是为什么我们在全局环境中声明一个变量和函数后,其会成为window对象的属性.

    var a = 11;
    function fn(){};
    
    window.a ===a ; // -> true 
    window.fn ===fn; // -> true 
    
    • activation object

    对于局部环境,我们通常用activation object来作为variable object .并且这个对象我们是不能用代码访问到的.这个对象中存储着我们在函数内部声明的变量和函数以及参数(包含隐式参数arguments).

    注: 下文会将其简写为AO
    

    我们还要说明一下,activation object在最开始的时候只有一个变量,即arguments对象(这个对象在全局环境上是不存在的).

    执行上下文的两个阶段

    常言道: 兵马未动 粮草先行

    因此当我们进入一个执行上下文的时候,js解释器并不会立即就去执行其内部代码, 而是先做一些预备工作来保证之后的执行无后顾之忧.

    其大致可分为两个阶段:

    • 准备阶段
      • 创建activation object
      • 创建Scope chain(即作用域链)
      • 设置this的值
      • 抽离出执行代码.(剔除声明部分的代码)
    • 激活/执行阶段
      • 设置变量的值、函数的引用,然后解释/执行代码

    然后下文中我们会着重讲一下准备阶段中一些过程的细节.

    AO(activation object)的创建过程

    1. 在创建activation object之初,就为其添加了一个名为arguments对象,里面存储着函数调用时传入的所有参数.

    2. 扫描函数的形式参数

      • 对于所有找到的参数,将由参数名和其对应值以key-value的形式存入VO/AO中,如果没有传递对应参数的话,那么将参数名和undefined值以key-value的形式存入VO/AO中.
    3. 扫描函数内部代码,查找函数声明(Function declaration).

      • 对于所有找到的函数声明,将函数名和函数引用存入VO/AO中.
      • 并且如果VO/AO中已经有同名的标识符, 那么就对其标识符的引用进行覆盖.
    4. 扫描函数内部代码,查找变量声明(Variable declaration)

      • 对于所有找到的变量声明,将变量名存入VO/AO中,并初始化为"undefined".
      • 如果变量名称跟已经声明的参数或函数名相同,则变量声明不会干扰已经存在的标识符和其引用.

    我们以如下代码为例:

    function add(a,b){
        var z ;
        function fn(){
            console.log(a,b)
        }
        z = a+b;
        return z;
    }
    
    add(10,20)
    

    接着我们来分析一下AO的创建过程, 如图:

    创建AO过程创建AO过程

    然后我们来验证一下当标识符命名发生冲突时会怎样:

    • 参数名和函数名冲突
        function fn(x){
            function x(){};
            console.log(x)
        }
        
        fn(10) // -> ƒ x(){}
    

    我们会发现当参数名和函数名冲突时,函数的引用值会覆盖参数名的引用值.

    • 参数名和变量名冲突
       function fn(x){
                var x ;
                console.log(x)
            }
            
        fn(10) // -> 10
    

    我们会发现当参数名和变量名冲突时,变量声明不会干扰已经存在的标识符和其引用.

    • 变量名和函数名冲突
       function fn(){
                 function x(){};
                 var x ;
                console.log(x)
            }
            
        fn() // ->  ƒ x(){}
    

    这一结果又刚好进一步证明了我们上一步得出结论.

    我们再来看最后一段代码:

    function fn(){
        console.log(x)
        var x = 10 ;
    }
    fn() // -> undefined
    

    我们看见了,其输出的结果是undefined. 不知你有没有发现,上述代码与变量提升有关.而AO的创建过程则正是造成变量提升这一现象的根源.

    函数对象的[[scope]]属性

    在JavaScript中, 一切皆为对象

    JavaScript 中每个函数都表示为一个函数对象(函数实例),既然是对象,就有相关的属性和方法。除了正常的属性,函数对象具有仅供 JavaScript 引擎内部使用,但不能通过代码访问的一系列内部属性。这些属性中,其中一个就是 [[scope]] 属性。

    函数内部的 [[scope]]属性实际是一个variable object的集合.其包含了当前执行环境栈中所有执行上下文variable object. 而这也正是作用链的雏形.

    注意: 因为该函数还未调用,所以执行环境栈中没有这个函数的执行上下文.
    

    作用域链

    正如上文所述, 执行上下文中有很多对我们来说十分重要的数据. 除了variable object以外 ,作用域链也是其中之一.

    • 创建作用域链的过程:
    1. 将函数内部[scope]属性中的所有对象按顺序复制到作用域链中,完成作用域链的初始化.
    2. 将当前执行上下文variable object放入作用域链的顶端,彻底完成作用域链的创建.

    正因此,当前执行上下文的variable object一直都是其作用域链的开头 .

    • 标志符的查询

    当在某个环境中为了读取或是写入而引用一个标志符,必须通过搜索来确定标识符代表什么. 搜索过程从作用域链的顶端开始,向上逐级查询与给定名字匹配的标识符.

    在此我们再详细说明一下其搜索过程的细节. 如果在局部环境中找到了该标识符,搜索就会停止,变量就绪. 若是没有找到该变量名/函数名 ,那么就会沿着作用域链查找.搜索过程会一种追溯到全局对象上,若是还没有查找到该标识符,那么便意味着该变量/函数并未声明,输出错误信息.

    var a = 10 ;
    var b = 100;
    function fn(){
         var b = 20 ;
         console.log(a) // -> 10 
         console.log(b) // -> 20
    }
    fn()
    

    以上段代码为例,其在沿着作用域链上的查询过程如图:

    作用域链作用域链

    结束语

    写这篇文章的原因来自一次偶然的思考, 然后便是大量的翻阅相关资料.甚至为此我还用我那有些蹩脚的英文翻墙去查阅一些外文资料.其过程自然有些艰辛的,但是黄天不负有心人,我总算是理解了JavaScript中的一些核心概念.并且我能感受的到,一扇新的大门即将为我开启, 征程还未停止.不过我有些喜欢上了这种在路上的感觉了.

    我在路上我在路上

    相关文章

      网友评论

        本文标题: js深度剖析 : 执行环境和作用域链

        本文链接:https://www.haomeiwen.com/subject/bgjrjxtx.html