美文网首页
JS编译机制:语法树、AO、GO

JS编译机制:语法树、AO、GO

作者: 江平路 | 来源:发表于2020-08-20 21:31 被阅读0次

    JS运行步骤

    JS有两个特性,一个是单线程,一个是解释性语言。不同于编译性语言,解释性语言通常理解为不整体编译,由解释器解释一句执行一句。但JS不是直接对着代码解释执行,在解释执行之前,需要进行其他的步骤。

    1.语法分析和语法分析,建立上下文关系的语法树;

    1.  var a =  1,b=2;
    2.  /*解析后的语法树
    3.  {
    4.  "type": "Program","body": [
    5.  {
    6.  "type": "VariableDeclaration","declarations": [
    7.  { 
    8.  "type": "VariableDeclarator","id": {
    9.  "type": "Identifier","name": "a" 
    10.  },"init": {
    11.  "type": "Literal","value": 1,"raw": "1" 
    12.  } 
    13.  },{ 
    14.  "type": "VariableDeclarator","name": "b"
    15.  },"value": 2,"raw": "2"
    16.  } 
    17.  }
    18.  ],"kind": "var" 
    19.  } 
    20.  ] 
    21.  } 
    22.  */
    

    json格式的语法树,每个对象就是一个节点(Node)。可以看到每个节点都有一个type属性用来标识当前节点的类型。

    Program类型,是语法树的根节点,只有一个。它具有body属性包括所有的语法节点。然后整个的var a = 1,b=2;是一个变量声明语句(VariableDeclaration)。这个语句里面有一些变量声明(VariableDeclarator),这边就是a=1和b=2;变量声明符下面有两个属性,分别是id和init。也就是对应的左边的被赋值者和右边的值。Identifier与Literal都可以表示最小的单位,他们不再包含其他节点。

    2.预编译

    预编译有个特点:任何变量,如果未声明就赋值,那该变量为全局变量,即暗示全局变量(imply global)。并且所有的全局变量都是window的属性。

    (1)产生GO对象

    GO对象全称为 global object(全局对象,等同于window),在开始预编译时产生的对象,比AO对象先产生,用于存放全局变量,也称为全局作用域。

    GO预编译三步骤
    1. 生成GO对象
    2. 将变量声明的变量名当做GO对象的属性名,值为undefinded
    3. 将声明函数的函数名当做GO对象的属性名,值为函数体

    在javascript引擎在将语法检查后正确后生成的语法树复制进来之后,javascript引擎会对语法树当中的变量声明、函数声明以及函数的形参(var、function的变量提升在预编译阶段,var只声明,function声明并赋值)进行属性填充,这过程是发生在执行代码之前,也就是为真正的解析阶段做好准备,这既是为什么此阶段叫“预编译”的原因

        <script>
            test();//a,预编译阶段function声明并赋值,执行阶段可以运行
            function test(){
                console.log('a');
            }        
        </script>
    
        <script>
            console.log(a);//undefined,预编译阶段var声明没有赋值赋值,执行阶段才给变量a赋值
            var a = 123;
        </script>
    

    实战解析

        <script>
            //生成GO对象:GO{}
        console.log(a);//function a () {},预编译阶段给a赋值函数
            var a = 123;//执行阶段才给变量a赋值,执行前都是undefined
            function a () {
            }//预编译阶段已经声明并赋值,且函数没有立即执行,所以js解释执行阶段跳过此步
            console.log(a);   //执行阶段a被赋值为123  
        </script>
    
    

    1、生成GO对象:GO{}
    2、把var开头的声明变量添加进GO对象,不做赋值:
    GO{
    a:undefined;
    }
    3、将声明函数添加进GO对象,如果该变量已经被var声明过,则覆盖之前声明的变量,并赋值函数地址:
    GO{
    a:function a () {};//覆盖a的值
    }

    (2)函数的AO对象(GO预编译结束并开始执行代码,当遇到函数执行前进行函数预编译)

    AO对象全称为:activation object (活跃对象/执行期上下文),当GO预编译完成之后,js开始执行,当遇到函数执行表达式(如:test())时,在函数执行前执行函数预编译,此时会产生一个AO对象,AO对象保存该函数的参数变量。

    函数预编译步骤

    1.产生AO对象
    2.将函数的参数以及函数里面声明的变量当做AO对象的属性名,值全部为undefined。
    3.将实参的值赋值给形参。
    4.在函数里面声明的函数,函数名作为AO对象的属性名,值赋值给函数体。(若参数名和函数名重叠,则函数体值会覆盖参数值)

    实战解析

        <script>
            function test(a) {
                //形参和var声明提前,并给形参赋值
                //function声明,如有相同变量,和覆盖
                console.log(a);//function(){}
                var a = 2;//赋值给a,则AO对象中a的值被覆盖为2
                console.log(a);//2
                function a () {}//预编译阶段覆盖形参a,被赋值为函数,执行阶段跳过
                console.log(a);//2
                var b = function () {};//执行阶段b被赋值
                console.log(b);//function(){}
                function d(){}
            }
            test(1);
        </script>
    

    1、创建AO对象
    AO{
    //此时AO对象为空
    }

    2、确定AO对象的属性名
    AO{
    a:undefined; //函数参数
    b:undefined; //函数里面声明的变量
    }
    3、将实参值赋值给形参
    AO{
    a:1; //函数参数
    b:undefined; //函数里面声明的变量
    }

    4、处理函数里面的声明函数
    AO{
    a: function a () {} //变量名一样,值覆盖
    b:undefined; //函数里面声明的变量
    d:function d () {};
    }

    实战深入理解AO、GO

            console.log(a);//undefined
            a = 100;
            function test() {
                console.log(a);//优先找到函数作用域中声明的a,如找不到,往上层找
                a = 200;
                console.log(a);//函数内a被赋值200
                var a = b = 300;//函数内a被赋值300,b没有声明,是全局变量,赋值为300
            }   
            test();
            console.log(b);//全局变量,300
            var a;
            console.log(a);//100,未操作全局变量a
    
    

    1、生成GO对象:GO{}

    2、将var声明变量添加进GO对象内,值为undefined
    GO{
    a:undefined;
    }

    3、将function声明函数添加进GO对象呢,值为函数体
    GO{
    a:undefined;
    function test() { … };
    }

    4、预编译完成,执行代码:输出a,此时a的值为:undefined

    5、a赋值,此时GO对象内的a值变为100
    GO{
    a:100;
    function test() { … };
    }

    6、执行函数test(),生成AO对象:AO{}
    7、将var声明的变量添加进AO对象,值为undefined
    AO{
    a:undefined;
    }

    8、函数没有传递参数,跳过函数预编译的第三步
    9、函数里面没有声明函数,跳过函数预编译的第四步

    10、执行函数,打印a,此时AO里面有属性a,则输出AO里面a的值,即输出: undefined

    11、AO的a赋值200,此时AO的a的值为200
    AO{
    a:200;
    }

    12、将300的值赋值给b再赋值给a,此时b未声明,所以b为全局变量,将b添加进GO对象内,将a的值改为300
    GO{
    a:100;
    function test() { … };
    b:300;
    }
    AO{
    a:300;
    }

    3.解释执行

    在解释执行阶段,遇到变量需要解析时,会首先从当前执行环境的活动对象中查找,如果没有找到而且该执行环境的拥有者有prototype属性时则会从prototype链中查找,否则将会按照作用域链查找。遇到var a = ...这样的语句时会给相应的变量进行赋值(注意:变量的赋值是在解释执行阶段完成的,如果在这之前使用变量,它的值会是undefined

    相关文章

      网友评论

          本文标题:JS编译机制:语法树、AO、GO

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