13. 预编译过程

215 阅读11分钟

预编译

通俗来说js特点:单线程、解释性语言(翻译一句,执行一句)

js运行三部曲

  1. 语法分析(扫描基础语法是否错误)
  2. 预编译
  3. 解释执行

函数声明、变量 声明提升

  • 函数声明 整体提升
  • 变量 声明提升
    //案例一

        //正常写法:
        function test1() {
            console.log("a")
        }
        test1();

        //异常写法:
        test2();
        function test2() {
            console.log("a")
        }
        //把函数写在执行下面,也不报错!
        /*
        真相:函数声明 整体提升:
            其实表面看起来test2()在函数前面就执行,
            但是编译器会把function test2(){console.log("a")}提前到执行前面运行。
            所以,不会报错~
        */

    //案例二
        //正常写法:
        var a = 123;
        console.log(a) //结果:123

        //异常写法:
        console.log(c) //结果:报错了~!打印一个为声明的变量,当然报错!

        //异常写法:
        console.log(b) //结果:undefined
        var b = 123;
        //也不报错,但结果是undefined。奇怪~
        /*
        真相:变量 声明提升:
        1. 看似console.log(b)在var b = 123前面执行,但实际编译的时候,会把var b = 123提前执行。
        这样console.log(b)就不会报错~
        2. var b = 123; 其实是2步:
             (1) var b;  变量声明的这一步,会提升。(所以,你会看到打印b的结果是:undefined)
             (2) b = 123; 赋值的这一步并不会提升
         */


    // 上面的现象都是预编译在作怪:
    // 1. 函数声明 整体提升
    // 2. 变量 声明提升

imply global 暗示全局变量

  • 任何变量,如果未经声明就赋值,此变量就为全局对象window所有
    abc = 123; //未声明abc变量,就直接赋值。
    document.write(abc); //打印也不会报错
    document.write(window.abc);  //abc未声明就赋值,abc就会属于window对象

    //连等的情况下,有意思的事情发生了!
    function test() {
        var a = b = 5;
        console.log("我是a,我在函数俩面,我的值是:"+ a)
        console.log("我是b,我在函数俩面,我的值是:"+ b)
    }
    test();
    console.log("函数外面,可以访问test函数内部定义的a变量吗?" + window.a) //结果:undefined
    console.log("函数外面,可以访问test函数内部定义的b变量吗?" + window.b) //结果:5

    //为啥b的值可以访问到?
    /*
    因为:var a = b = 5 这一条拆分开,其实是2步骤:
    1. b = 5 这一步, b未声明接直接赋值,就属于全局变量window的,所以函数外面也可以访问到!
    2. var a = 5 这一步可以需要拆成2步骤:
        2.1 var a; 这一步a的值是undefined(注意这是函数内部的局部变量a,外面访问不到!)
        2.2 a = 5; 这一步a的值是5
    */

  • 一切声明的全局变量,都是window属性
    //例子1
    var b = 234;
    document.write(window.b); //声明的变量b,也归window所有。
    
    //例子2
    function f() {
        var abc = 123;
    }
    f();
    console.log(window.abc) //结果:undefined 因为abc是函数f内的局部变量

函数的预编译过程

  • 预编译发生在函数执行的前一刻
  • 预编译四部曲
    1. 创建AO对象(Activation Object 活跃对象,叫执行期上下文)
    2. 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
    3. 将实参值和形参值统一
    4. 在函数体里面找到函数声明,值授予函数体。
   //函数编译过程中,形参、函数名、变量名 冲突的时候,谁的优先级高?
    function fn(a) {
        console.log("第一次输出:" + a  ); //结果:function a() {}

        var a = 123;

        console.log("第二次输出:" + a);

        function a() {}

        console.log("第三次输出:" + a);

        var b = function () {};

        console.log("第四次输出:" + b);

        function d() {}
    }
    fn(666);
    /* 结果:
    第一次输出:function a() {}
    第二次输出:123
    第三次输出:123
    第四次输出:function () {}
    */

    /*
    探究里面的函数编译过程:
    1.创建AO对象
        AO {
          空的
        }
    2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined。
        AO {
            a : undefined,
            b : undefined
        }

    3. 将实参值的形参值统一(也就是把实参的具体值,传给函数的形参,并替换值)
        AO {
            a : 666,
            b : undefined
        }
    4. 在函数体里,找到函数声明。值授予函数体。
        4.1 找到函数中的函数声明,其实就找到了2个。
            1. function a() {}
            2. function d() {}
        4.2 把4.1找到的函数声明的"名"(也就上面的:a 和 d),作为AO对象的属性名,值授予函数体。
            4.2.1 其中a的属性名,已经有在第3步,创建叫a的属性名了,这一步就不用创建了。
            4.2.2 那么a的值呢?值授予函数体,函数体是:function a() {}

            4.2.3 其中d的属性名,没创建过,所以这一步要创建上。
            4.2.3 那么d的值呢?值也是授予函数体,函数体是:function d() {}
    5. 所以最终AO对象变成了这样:
            AO {
                a : function a() {},
                b : undefined,
                d : function d() {}
            }

    6. 上面编译过程结束,开始顺序执行函数。

        console.log("第一次输出:" + a  ); //结果:function a() {}
        因为此时的a在AO对象里面,所以确实应该输出:function a() {}

        var a = 123 分成2步骤
        1. var a;  这一步在编译的时候就已经完成了(变量名提升)
        2. a = 123 这一步需要执行,最终把AO对象中的a给改变为了123,如下:
            AO {
                a : 123,
                b : undefined,
                d : function d() {}
            }
        console.log("第二次输出:" + a); //结果:123

        function a () {} 这一步已经在编译的时候就提升上去了,所以这一步不用执行了。

        console.log("第三次输出:" + a); //结果:123

        var b = function () {} 分2步骤
        1. var b ; 这一步在编译的时候就已经完成了(变量名提升)
        2. b = function () {} 这一步需要执行,最终把AO对象中的b的值改变了:
             AO {
                a : 123,
                b : function () {},
                d : function d() {}
            }

        console.log("第四次输出:" + b);//结果: function () {}
    */

//练习一
    function test(a,b) {
        document.write(a);
        c = 0;
        var c ;
        a = 3;
        b = 2;
        document.write(b);
        function b() {}
        function d() {}
        document.write(b);
    }
    test(1)

    /*
    1. 经过4不函数编译过程后AO对象:
        AO {
            a : 1,
            b : function b() {},
            c : undefined,
            d : function d() {}
        }
     2. 依次按顺序执行函数内容:
        第一行:document.write(a); 应该a的值是1
        第二行:c = 0 ,会把AO对象改为:
            AO {
                a : 1,
                b : function b() {},
                c : 0,
                d : function d() {}
            }
        第三行:var c ; 不会执行,因为这一步已经提前执行过了(变量 声明提升)
        第四行:a = 3; 会把AO对象改为:
            AO {
                a : 3,
                b : function b() {},
                c : 0,
                d : function d() {}
            }
        第五行:b = 2; 会把AO对象改为:
            AO {
                a : 3,
                b : 2,
                c : 0,
                d : function d() {}
            }
        第六行:document.write(b); b的结果应该是2
        第七行、第八行 不会执行,因为提前执行过了(函数声明 整体提升)
        第九行:document.write(b); b的结果应该还是2
    */

全局的预编译过程

  • 预编译3部曲
    1. 创建GO对象(Global Object 全局上下文)
    2. 找变量声明,将变量作为GO属性名,值为undefined。
    3. 在函数体里面找到函数声明,值授予函数体。
    //全局也会发生预编译,3步曲。
    console.log("3步曲后编译后,全局变量a的值是:" + a);
    var a = 123;
    function a() {}
    console.log("顺序执行,最终a的值是:" + a);

    /* 结果:
    3步曲后编译后,全局变量a的值是:function a() {}
    顺序执行,最终a的值是:123
    */

    /*
    细节:
    1. 3步曲后,GO对象:
       GO {
          a : function a() {}
       }
    2. 顺序执行:
        第一步: console.log("3步曲后编译后,全局变量a的值是:" + a);
                所以结果是:function a() {}
        第二步: var a = 123; 分2步
            1. var a; 这一步提前就做了,不需要做了(变量 声明提前)
            2. a = 123; 这一步需要做,GO对象此时变成:
               GO {
                    a : 123
               }
         第三步: function a() {} 不会执行,因为提前就做了(函数声明 整体提升)
         第四步:console.log("顺序执行,最终a的值是:" + a);
                所以结果是:123
    */
  • 全局上下文GO对象 === window 对象(GO就是window,所以说:任何全局变量都是window属性)
 //编译过程,结合imply global 实例
    function test() {
        var a = b = 123;
        console.log(window.b);
        console.log(window.a);
        console.log(a);
        console.log(b);
        var b = 456;
        console.log(b);


    }
    test()

    /*
    1. 函数编译4步曲,最后AO对象:
        AO {
            a : undefined

        }
    2.按顺序执行
    var a = b = 123; 拆分2个部分:
    第一部分:b = 123 此时b没有声明就赋值,变成了imply global,此时GO对象产生了:
        GO {
            b : 123
        }

    第二部分 var a = 123 ,分成2个部分:
        1. var a; 这一步不会执行,因为提升了(变量 命名提升)
        2. a = 123 这一步会执行, AO变成了这样:
          AO {
            a : 123
          }
    打印:
    console.log(window.b); 结果123,因为GO==window,所以GO对象中b的值确实是123。
    console.log(window.a); 结果undefined,因为GO对象中,根本就没有a属性。所以找不到~
    console.log(a); 结果 123,因为在函数内部打印,就是在打印AO中的a属性,AO中的a属性就是123.
    console.log(b); 结果 123,因为此时实际打印的是GO中的b[ console.log(b)写法相当于console.log(window.b)]

    var b = 456; 这一步相当于,在AO对象中,创建了新的b属性,此时AO对象:
          AO {
            a : 123
            b :456
          }
    再次打印:console.log(b); 这时候,实际打印的是AO中的b属性(而不是GO中的b属性),因为如果能在AO中找到的属性名,肯定优先打印(局部变量的优先级高于全局变量)。
    结果:456

    */
//练习二
   console.log(test)
   function test(test) {
       console.log(test);
       var test = 234;
       console.log(test);
       function test() {}
   }

   test(1);
   var test = 123;

   /*结果:
   ƒ test(test) {
       console.log(test);
       var test = 234;
       console.log(test);
       function test() {}
   }
    test() {}
    234

    */

   /*
   细节:
   1. 解释器先扫描所有代码,发现有var test = 123 以及 function test(){xx},发现有变量和函数声明,所以就先创建GO对象
   GO {
        test : function test(test) {
                  console.log(test);
                  var test = 234;
                  console.log(test);
                  function test() {}
               }
   }

   2. 顺序执行代码
       第一行:console.log(test) 执行 结果是:function test(text){....} 显而易见,从GO对象中查找。
       第二行:   function test(test) {...} 不会执行,因为函数声明 整体提高原则,早在编译的时候就执行了。
       第三行:test(1) 这一步会执行
              1. 创建AO对象,经过4步曲,创建AO对象内容为:
                   AO {
                       test : function test() {}
                   }
               2. 顺序执行函数:
                    第一行:console.log(test); 结果肯定是:function test() {}
                    第二行:var test = 234; 拆分2个步骤
                          1. var test; 不会执行,因为 变量 声明提高提高原则,早在编译的创建AO对象的时候就执行过了,所以这里不再执行。
                          2. test = 234 这一行会执行,AO对象变为:
                             AO {
                                test : 234
                             }
                    第三行:console.log(test); 结果肯定为234
    */
//练习三
    //百度面试题一
    function bar() {
        return foo;
        foo = 10;
        function foo() {}
        var foo = 11;
    }
    document.write(bar());

    //百度面试题二
    document.write(bar());
    function bar() {
        foo = 100;
        function foo() {}
        var foo = 11;
        return foo;
    }


//练习
    a = 100;
    function demo(e) {
        function e() {}
        arguments[0] = 2;
        document.write(e);
        if(a){
            var b = 123;
            function c() {} //理论来讲if中不能声明函数的~但就当练习!
        }
        var c;
        a  = 10;
        var a;
        document.write(b);
        f = 123;
        document.write(c);
        document.write(a);
    }
    var a;
    demo(1);
    document.write(a);
    document.write(f);

    /*结果:
    2
    undefined
    undefined
    10
    100
    123*/

    /* 具体细节如下:
    1. 生成全局GO对象
        GO {
            demo : function demo(e) {..}
            a : undefined
        }
    2. 顺序执行
        第一行:a = 100,此时GO对象变为:
        GO {
            demo : function demo(e) {..}
            a : 100
        }
        第二行:function demo(e) {..} 不会执行,因为函数声明提升,在初始GO对象就执行过了。
        第三行:var a; 不会执行,因为变量声明提升。
        第四行:demo(1);
            (1) 初始AO对象
            AO {
                e : function e() {..},
                b : undefined,
                c : undefined (练习的时候,可以把C想成:function c() {..}, 仅仅为了练习,其实if中不能包含函数声明)
                a : undefined,
            }
            (2) 顺序执行函数:
                第一行:function e() {} 不会执行,因为函数声明提升。
                第二行:arguments[0] = 2; arguments[0] 这个就是形参e,把形参e给变成了2。此时AO对象变成了:
            AO {
                e : 2,
                b : undefined,
                c : undefined (function c() {..},)
                a : undefined,
            }
                第三行:document.write(e); 结果:2
                第四行:if判断a的值,但是此时a的值是undefined,所以if内的语句不会执行。
                第五行:var c; 不会执行,变量声明提升
                第六行:a  = 10; 会执行,此时a的值改变,AO对象变成了:
                AO {
                    e : 2,
                    b : undefined,
                    c : undefined (function c() {..},)
                    a : 10,
                }
                第七行:var a; 不会执行,变量声明提升
                第八行:document.write(b); 结果:undefined
                第九行:f = 123; 未声明就赋值(imply global),修改了GO对象,此时:
                GO {
                    demo : function demo(e) {..}
                    a : 100
                    f : 123
                }

                第十行:document.write(c); 结果:undefined (在AO对象中查找)
                第十一行:document.write(a);  结果:10 (在AO对象中查找)
         第五行:document.write(a); 结果:100 (在GO对象中查找)
         第六行:document.write(f); 结果:123 (在GO对象中查找)
    */