美文网首页
单信js——3预编译和函数执行,作用域链(重点)

单信js——3预编译和函数执行,作用域链(重点)

作者: 随风飞2019 | 来源:发表于2019-08-06 10:19 被阅读0次

    函数的执行顺序
    1.语法分析(扫描一遍,看有没有语法错误之类的)
    2.预编译
    3.解释执行(解释一行,执行一行)

    预编译和执行是分不开的,预编译就发生在函数执行的前一刻。预编译完成,马上逐行解释执行。下面讲预编译,里边含有解释执行的东西。
    先铺垫几个知识点
    函数声明整体提升
    变量,声明提升
    暗示全局变量,任何变量如果未经声明就赋值,此变量归全局所有。一切声明的全局变量,全是window的属性,比如b=3;意思就是window.b=3

    变量,声明提升
    var a=123相当于,var a;a=123;变量,
    声明提升,把var a提升了,打印的话就是undefined
    

    预编译四部曲:
    1.创建AO对象
    2.找形参和变量声明,将形参名和变量名作为AO属性名,增加到AO里面,并且赋值undefined
    3.将实参赋值给形参
    4.在函数体里找函数声明,值赋予函数体

    第一步:
    生成一个活动对象(Active Object),简称AO,又叫做执行期上下文
    函数在运行前,会执行词法分析

    第二步:
    分析形参和变量声明,如var age;或var age=25;
    AO.age=undefine

    第三步:
    将实参赋值给形参,如果实参age=30,那么AO里面也跟着变化,这时候
    AO.age=30

    第四部:
    分析函数的声明,如果有function age(){}
    把函数赋给AO.age ,覆盖上一步分析的值

    下面是一个例子,详细讲述函数执行三部曲,AO执行四部曲的
    <script>
        function fn(a) {
          console.log(a);
          var a=123;  //相当于var a并且赋值为123
          console.log(a);//第三行
          function a() {    } //这个叫函数声明
          console.log(a);
          var b=function () {    }  //这个叫函数表达式,是b的声明赋值
          console.log(b);
          function d() {   }  //这个叫函数声明
        }
        fn(1);
        //首先进行语法扫描分析,没发现语法错误,就开始预编译,然后逐行解释执行
        //下面重点讲预编译四部曲
        /*1.创建AO对象
        2.找形参和变量声明,将形参名和变量名作为AO属性名,
           增加到AO里面,并且赋值undefined,这时候
        AO:{
            a:undefine,(形参是a,变量声明也有var a,那就只留一个)
            b: undefine,(变量声明var a和var b)
        }
        3.将实参赋值给形参,这时候
        AO:{
            a:undefine—>1,(实参a是1,这时候a=1)
            b: undefine
        }
        4.分析函数的声明,如果有同名的就覆盖,因为函数声明优先级最高,这时候
        AO:{
            a:undefine—>1—>function a() {    },(有同名的函数声明,就覆盖)
            b: undefine,(b不变)
            d:function d() {   } (增加了一个d,属于函数声明)
        }
        下面进入解释一行,执行一行的阶段,已经看过的就不再看了(比如var a)
        第一行:console.log(a);从AO里边找,打印出函数体
        第二行,var a已经不看了,只剩下赋值123了,这时候AO有变化
        AO:{
            a:undefine—>1—>function a() {  }—>123,
            b: undefine,
            d:function d() {   }
        }
        第三行:console.log(a)打印出123
        第四行:也不看了,之前已经提升过了
        第五行:console.log(a)也打印出123
        第六行:var b就不看了,只是把b赋值为函数体,这时候又修改AO
        AO:{
            a:undefine—>1—>function a() {  }—>123,
            b: undefine—>function () {    },
            d:function d() {   }
        }
        第七行:console.log(b);打印出函数体function () {    }
        第八行:不看了,之前提升过了。
        视频教程可以参考渡一教育递归,预编译(上)
        */
    </script>
    

    预编译不仅发生在函数内,也发生在全局

    下面是单信老师课件部分:

    • 编译原理
      软件开发语言的编译模式分为:
      编译型编程语言:代码开发好以后,执行编译命令,将编译的文件下载到电脑上安装即可使用
      如:c语言, c++, Java....
      解释型编程语言:代码开发好以后,不执行编译。 在执行代码时才进行编译,解释一句执行一句
      如:JS php jsp asp .net ...
      js执行两个阶段:
      预编译阶段:将函数和变量定义进行提升(将函数和变量的定义提到作用域的第一句之前执行)。 扫描上下文环境,如果有语法错误有报错。
      解释执行阶段:一句一句执行

    作用域:
    根据上下文环境(函数会产生独立的运行环境),将作用域划分为:

    1. 全局作用域
      函数外定义的所有内容(函数、变量)都是全局作用域,在任何地方都可以使用

    2. 局部作用域
      函数内定义的内容都是局部作用域,只能在函数内部使用

    3. 块级作用域
      在ES5中没有块级作用域的概念,可以使用IIFE(立即执行函数表达式)实现块级作用域
      在ES6中,可以使用 let实现块级作用域

    //全局作用域
    var a='hello222'; //对于全局变量来说,要不要var关键字都可以,a就是window的属性
    var fn=function(){
    //局部作用域
    var b="world";
    };
    fn();

    看下面一个例子,求1-100的和,说为什么要引入块级作用域
    var sum=0;
    for(var i=1; i<=100; i++){
    sum+=i;
    }
    console.log(sum); //这是我想要的结果,但也附带的出现了i这个全局变量污染
    console.log(i); //但计算过程中的i,由于没块级作用域,变成一个全局变量。 产生全局污染。
    //总结,这样的计算方式会产生全局污染
    
    //改进版:将运算代码放在函数中,避免全局污染。必须返回一个值作为最终计算结果供全局使用
    //IIFE:立即执行函数表达式
    var sum=function(){
    var sum=0;
    for(var i=1; i<=100; i++){
    sum+=i;
    }
    return sum;
    }();
    //这样写的话,就增加了一个函数名,仍然有可能造成全局污染,也不是最好的处理办法。
    
    //经典IIFE的写法,再次改进
    (function(){
    var sum=0;
    for(var i=1; i<=100; i++){
    sum+=i;
    }
    window.sum=sum;
    })();
    //如果直接定义函数,就必须写函数名。想不写函数名就必须把函数定义变成函数表达式: + - ! (),这里就变成了一个立即执行函数表达式,最好是前面再加个分号 ;
    //这样处理后就只得到一个sum,并没有产生全局i和函数名污染
    为了更加方便的解决这个问题,引入了块级作用域let,不会形成变量提升
    
    //ES6可以直接使用let实现块级作用域
    var sum=0;
    for(let i=1; i<=100; i++){
    sum+=i;
    }
    console.log(sum);
    console.log(i);
     //使用ES6的let定义块级作用域,不会造成全局污染
    

    作用域链:

    函数操作时,先查找内部的数据,找不到再去找上一级作用域的数据,再找不到再上一级。形成一个查询数据的作用域链条,就叫作用域链。

    IIFE 立即执行函数表达式:

    1 什么是IIFE?
    IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。
    定义函数后立即执行一次,没有函数名,无法再次调用,也不会造成全局染污。
    2 IIFE有什么用?
    可以用于解决运算过程中产生的全局污染问题
    也是ES5中实现块级作用域的方式
    3 IIFE的特点
    将所有运算代码都放在匿名函数中进行运算
    函数是一个表达式,没有函数名,必须立即执行一次
    执行一次以后就消失,将所有过程产生的数据和变量一起销毁,不会造成全局污染
    4 IIFE的写法
    (function(){
    ...
    window.a=result
    })();
    下面还是那个求和的例子,讲如何使用IIFE和产生的效果
    //正常运算1到100的累加,想要的是运算结果,中间过程不管
    var sum=0;
    for(var i=1;i<=100;i++){
    sum+=i;
    }
    console.log(sum); //这个结果是想要的
    console.log(i); //运算完以后i会一直遗留在全局上,造成全局污染

    //使用IIFE进行改进
    var s=(function(){
    var sum=0;
    for(var i=1;i<=100;i++){
    sum+=i;
    }
    return sum; //函数内计算的结果必须返回出去
    })();

    //另外一种用法
    (function(){
    var sum=0;
    for(var i=1;i<=100;i++){
    sum+=i;
    }
    window.s=sum; //函数内计算的结果必须返回出去,不写return,而是把结果赋值给window的属性s,这个是闭包的雏形了。
    })();
    console.log(s);

    阶乘

    阶乘是函数的一种形式,函数内部再次调用函数自身
    它必须有退出循环执行的条件,必须有进入执行的条件,
    经典写法如下,求5的阶乘,就是54321
    function fn(n) {
    if(n===1){
    return 1
    }
    return n*arguments.callee(n-1)
    }
    console.log(fn(10));
    arguments.callee代替fn函数自身

    闭包:

    1. 什么是闭包?
      闭包是一种作用域的体现,正常情况下函数内部可以访问函数外部的数据,而函数外部不能访问函数内部的数据;
      可以通过特殊写法(闭包):将函数内的子函数暴露到全局上,可以在外部调用并且可以访问函数内的数据。闭包可以让全局访问函数内的数据。
    2. 闭包的作用
      实现外部可以访问函数内部的数据
    3. 闭包的特征
      闭包一定是函数内嵌套函数
      闭包是一种作用域的体现,体现内部可以访问外部的数据,而正常情况下外部不能访问内部的数据
      闭包可以实现外部访问内部函数,内部函数访问上一级函数的数据。实现外部访问内部数据。
      闭包是IIFE的写法,由于内部数据被全局所调用,延缓资源回收。
    4. 闭包的写法
    5. 闭包会导致内存无法进行回收
    //闭包的写法,第一种
    (function(){
          var i=1;
          window.show=function(){
          return i++;
      }
    })();
    
    //闭包的写法,第二种
    var count=(function(){
         var i=1;
         return function(){
         return i++;
      }
    })();
    
    闭包

    当JavaScript引擎解析脚本时,分为“预编译”和“解释执行”两个阶段。
    1、“预编译”阶段。首先会创建一个环境的上下文对象,然后把使用var声明的变量,作为上下文对象的属性,以undefined先行初始化;使用function关键字声明的函数,也作为上下文对象的属性,定义出来,而函数则保留了定义的内容。----在这个过程中,函数定义的优先级 高于 变量定义。
    2、“解释执行”阶段。遇到变量解析,会先在当前上下文对象中找,如果没找到并且当前上下文对象有prototype属性,则去原型链中找,否则将会去作用域链中找。

    在js中,执行环境(execution context)是一个非常重要的概念。
    程序代码运行时,会产生一个专门用来管理所需的变量和函数的对象---环境对象,这个对象我们不能通过代码编写操作他,但解析器在处理数据时会在后台使用他。
    1、全局环境就是最外围的一个执行环境,可以在代码任意位置访问到。在浏览器中,全局环境就是window对象(因此,所有的全局变量和函数,都是window对象上的属性和方法)。
    2、局部环境以函数为主。每个函数都有自己独立的执行环境。
    局部作用域一般只在固定的代码片段内可访问到,最常见的就是函数内部,所以在很多地方就会有人把它称为函数作用域。

    相关文章

      网友评论

          本文标题:单信js——3预编译和函数执行,作用域链(重点)

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