zoukankan      html  css  js  c++  java
  • Js中的预编译

    什么是预编译?

        引擎会在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。

    1.预编译什么时候发生

      预编译分为全局预编译和局部预编译,全局预编译发生在页面加载完成时执行,而局部预编译发生在函数执行的前一刻。

      预编译阶段发生变量声明和函数声明,没有初始化行为(赋值),匿名函数不参与预编译 。只有在解释执行阶段才会进行变量初始化 。

      目的:定义作用域中的初始化词法环境、减少运行时报错

    2.预编译前奏

      一切声明的全局变量和未经声明的变量,全归window所有。 

      下面这个函数里面只有一个连等的操作,赋值操作都是自右向左的,而b是未经声明的变量,所以它是归window的,我们可以直接使用window.b去使用它。

    function test(){
    	// 这里的b是未经声明的变量,所以是归window所有的。
    	var a = b = 110;
            console.log(a,b);
    }
    test();
    console.log(a,b);//报错
    

    3.预编译步骤 

      首先JavaScript的执行过程会先扫描一下整体语法语句,如果存在逻辑错误或者语法错误,那么直接报错,程序停止执行,没有错误的话,开始从上到下解释一行执行一行。

      1.全局编译的步骤

    • 生成GO对象 GO{}(global object) 这个GO就是window
    • 将全局的变量声明(的名)储存一GO对象中,value为undefinde
    • 将全局的函数声明的函数名作为go对象中的key,函数整体内容为value储存到go对象中

      2.局部编译的步骤    

    • 执行前的一瞬间,会生成一个AO(action object)对象
    • 到函数体作用域里找形参和变量声明,将形参和变量声明作为AO对象的属性名,值为undefined
    • 将实参和形参统一
    • 分析函数声明,函数名作为AO对象的属性名,值为函数体,如果遇到同名的,直接覆盖  

    关于GO对象的例子:

    全局预编译:在逐行执行;语法检测之前

       var a;
        function fun(){
    
        }
        function abc(){
    
        }
        function a(){
    
        }
        console.log(a);
        var a = 100;
        console.log(a);

      1. 会生成一个对象(GO),这个对象封装的就是作用域,称为GO(global object)。当全部挂载完成之后,然后代码在去逐行执行

    GO{
    
    }
    

      2. 分析变量声明(var)——变量作为GO对象的属性名,值为undefined

    GO{
        a:undefined
    }
    

      3. 分析函数声明(function)——函数名作为GO对象的属性名,值为函数体(如果遇到同名,直接覆盖)

    GO={
      a:undefined;function a(){},
      fun:function fun(){},
      abc:function abc(){}                                                                                                                       
    }

      4. 当走到某一行的时候;a产生了一次赋值;此时GO对象变成了:

     GO={
          a:100,
          fun:function fun(){}
          abc:function abc(){};                      
    }

      5. 逐行执行(看着GO对象里面的执行)

    输出结果:ƒ a() {}   	100	

    关于AO对象的例子:

      概念:函数在每次运行时会重新创建函数内所有定义的变量,函数其实也是一种变量,因为它是变量名指向一个函数方法,所有的变量创建后加入到自身AO对象中,然后将在scope属性中加入自身AO对象。

      什么是AO:
        是函数执行前的一瞬间,生成一个AO对象(在函数执行前的一瞬间会生成自己的AO,如果函数执行2次,生成了两次AO,这两次的AO是没有任何关联)

    function fn(a) {
        console.log(a);//1(第一个 console.log())
        var a = 123;
        console.log(a);//2
        function a() {}
        console.log(a);//3
        var b = function() {}//函数表达式
        console.log(b);//4
        function d() {}
        var d = a;
        console.log(d);//5
      }
      
      fn(1);
      
    //输出结果:
    [Function: a]
    123
    123
    [Function: b]
    123

      1. 创建AO对象

    AO{
    
    }
    

      2. 到函数体作用域里找形参和变量声明,将形参和变量声明作为AO对象的属性名,值为undefined

    AO{
     a:undefined;
     b:undefined;
     d:undefined;
    }
    

      3. 将实参和形参统一

    AO{
     a:undefined; 1;
     b:undefined;
     d:undefined;
    }
    1. 在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体
    AO{
     a:undefined; 1; function (){ } 
       b:undefined;
     d:undefined; function() { }
    }
    

      逐行执行(看着AO对象里面的执行)

    第一个console.log:此时函数的执行环境内的变量为
    
    AO{
     a:undefined; 1; function (){ }
     b:undefined;
     d:undefined; function() { }
    }
    
    第二、三、四、五个console.log:此时函数的执行环境内的变量为
    
    AO{
     a:undefined; 1; function (){ }; 123;
     b:undefined;
     d:undefined; function() { }
    }

      解释:当函数调用时,创建函数执行上下文,然后根据实参填充arguments对象,即:形参var a = arguments[0],然后根据函数内的函数声明将函数名a进行提升,此时会发现函数名和形参名发生了冲突,由于形参与arguments中的数据共享状态,所以接下来后提升的函数名a内存放的函数引用地址会将前面定义的形参名a指向的arguments[0]直接覆盖掉

      函数执行完毕,销毁AO对象。

    在预编译这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后在进行接下来的处理,下面通过示例看下变量提升和函数提升

    变量的提升:

    示例1:

    function hoistVariable() {
        if (!foo) {
            var foo = 5;
        }
    
        console.log(foo); // 5
    }
    hoistVariable();
    

      预编译之后

    function hoistVariable() {
        var foo;
    
        if (!foo) {
            foo = 5;
        }
    
        console.log(foo); // 5
    }
    
    hoistVariable();
    

      引擎将变量声明提升到了函数顶部,初始值为undefined,自然,if语句块就会被执行,foo变量赋值为5

    示例2:

    var foo = 3;
    
    function hoistVariable() {
        var foo = foo || 5;
    
        console.log(foo); // 5
    }
    
    hoistVariable();
    

      foo || 5这个表达式的结果是5而不是3,虽然外层作用域有个foo变量,但函数内是不会去引用的  

      预编译之后:

    var foo = 3;
    
    function hoistVariable() {
        var foo;
    
        foo = foo || 5;
    
        console.log(foo); // 5
    }
    
    hoistVariable();

    函数的提升:

    示例1:

    function hoistFunction() {
        foo(); // 2
    
        var foo = function() {
            console.log(1);
        };
    
        foo(); // 1
    
        function foo() {
            console.log(2);
        }
    
        foo(); // 1
    }
    
    hoistFunction();
    

      第一次调用时实际执行了下面定义的函数声明,然后第二次调用时,由于前面的函数表达式与之前的函数声明同名,故将其覆盖,以后的调用也将会打印同样的结果  

      预编译之后

    function hoistFunction() {
        var foo;
    
        foo = function foo() {
            console.log(2);
        }
    
        foo(); // 2
    
        foo = function() {
            console.log(1);
        };
    
        foo(); // 1
    
        foo(); // 1
    }
    
    hoistFunction(); 

    示例2:

    var foo = 3;
    
    function hoistFunction() {
        console.log(foo); // function foo() {}
    
        foo = 5;
        
        console.log(foo); // 5
    
        function foo() {}
    }
    
    hoistFunction();
    console.log(foo);     // 3
    

      可以看到,函数声明被提升至作用域最顶端,然后被赋值为5,而外层的变量并没有被覆盖  

      预编译之后:

    var foo = 3;
    
    function hoistFunction() {
       var foo;
    
       foo = function foo() {};
    
       console.log(foo); // function foo() {}
       
       foo = 5;
    
       console.log(foo); // 5
    }
    
    hoistFunction();
    console.log(foo);    // 3

      函数的优先权是最高的,它永远被提升至作用域最顶部,然后才是函数表达式和变量按顺序执行

  • 相关阅读:
    beta冲刺1
    凡事预则立-于Beta冲刺前
    SDN第二次作业
    事后诸葛亮(团队)
    SDN第一次上机作业
    冲刺总结随笔
    Alpha第三天
    Alpha第二天
    Alpha冲刺博客集
    项目需求分析(团队)
  • 原文地址:https://www.cnblogs.com/yxkNotes/p/15728035.html
Copyright © 2011-2022 走看看