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

2020-08-20  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读