T-JS核心-day04-JS匿名函数、作用域和作用域链、闭包

四、Function对象

1. 匿名函数

  1. 什么是:定义函数时,不指定函数名的函数
  2. 为什么:2大优点
    1. 节约内存
    2. 避免产生全局变量,造成全局污染
  3. 何时用:2种情况
    1. 几乎所有回调函数都要定义为匿名函数
    2. 匿名函数自调用
  4. 如何用:2种情况
    1. 回调函数
      1. 什么是:自己定义了函数,但是不由自己调用,而是交给另一个函数,由另一个函数按需自动调用-----给别人用的函数
        举例1:想将一个数字内容的数组升序排列
        arr.sot(function(a,b){return a-b})
        

        补充
        sort() 方法,有一个可选参数,必须是函数,供它调用。那么就是个回调函数咯!回调函数的参数要有两个:第一个参数的元素肯定在第二个参数的元素前面!!!这个方法的排序是看回调函数的返回值: 如果返回值大于 0,则位置互换。 如果返回值小于 0,则位置不变。
        举例2:想根据不同的敏感词,动态选择不同的新值替换
        str=str.replace(/正则表达式/,function(keyword){return 不同新值})
        
      2. 为什么回调函数都要定义为匿名函数:为了节约内存!
    2. 匿名函数自调:
      1. 什么是:定义一个匿名函数后,立刻调用该函数执行,调用后立即释放
      2. 为什么:避免产生全局变量,造成全局污染
      3. 何时用:今后一切js代码都应该放在一个大的匿名函数自调内!尽量不要使用全局变量!
      4. 如何做:
        1. 标准写法:
          var 变量名=(function(形参变量列表){
              ...return 返回值
          })(实参值列表);
          
          创建新函数,立刻调用执行
          因为该函数没有名字,所以调用后,立刻释放
        2. 非主流写法:
          +function(){...}()
          !function(){...}()

          强调:结尾的(),必须要加,表示调用
      5. 示例:使用匿名函数自调避免全局污染
          <script>
            // 全局变量,记录程序执行的总时间
            var t=0;
            // 第一段程序:执行0.3s
            !function(){
              t+=0.3;
              console.log(`任务一耗时0.3s`);
            }();
            // 假设之后在此处添加了以下代码,这里出错了,需要记录出错时间
            (function(){
              // 局部变量,碰巧和全局变量重名了,也不会影响到全局变量
              var t=new Date();
              console.log(`这里出错了,在${t.toLocaleString()}`)
            })();
            // 第二段程序:执行0.8s
            +function(){
              t+=0.8;
              console.log(`任务二耗时0.8s`);
            }();
            console.log(`共耗时${t}s`);
          </script>
        
        输出结果:
        任务一耗时0.3s
        这里出错了,在2020/5/4 下午8:21:04
        任务二耗时0.8s
        共耗时1.1s

2. 作用域和作用域链

  1. 什么是作用域(scope):
    1. 从作用是说:作用域就是一个变量的可用的范围
    2. 从本质上说:作用域其实保存变量的特殊对象
  2. 为什么用作用域:为了避免不同范围之间的变量互相干扰
  3. JS中包括2级作用域
    1. 全局作用域

      1. 什么是:在程序任何位置都可以随意访问的,专门保存全局变量的存储空间
      2. 在浏览器中,全局作用域由window对象担当
      3. 当打开网页的一刹那,浏览器就会自动提前创建window对象,等待着保存后续创建的所有全局变量
      4. 我们创建的所有全局变量都默认保存在window对象中
      5. 保存在window对象中的变量,可在程序的任何位置被访问到
      6. 示例:验证全局变量到底存在哪里
          <script>
            // window
            var a=10;
            var fun=function(){console.log(1)};
            var arr=[1,2,3];
            console.log(a);
            console.log(fun);
            console.log(arr);
            console.log(window);
            console.log(window.a);
            console.log(window.fun);
            console.log(window.arr);
          </script>
        
        输出结果:
        10
        ƒ (){console.log(1)}
        (3) [1, 2, 3]
        Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
        10
        ƒ (){console.log(1)}
        (3) [1, 2, 3]
        执行过程:
        在这里插入图片描述
    2. 函数作用域

      1. 什么是:专门保存仅函数内可用的局部变量的存储空间

      2. JS中一个函数的作用域不调用是不存在的!只有调用函数时,才临时创建该函数的作用域,临时保存局部变量,函数调用后,函数作用域对象以及本次函数调用使用的局部变量一同释放。

      3. 行数作用域对象创建和释放的过程----4步

        1. 当定义一个函数时:
          都会创建一个函数对象,在每个函数对象上都会保存一个作用域链(好友列表)
          在这里插入图片描述

        2. 当调用函数时:

          1. 会临时创建一个函数作用域对象
          2. 在函数作用域对象中添加函数中所需要的的所有局部变量:这里的局部变量包含2种情况
            a. 在函数内var出的变量
            b. 函数的形参变量
          3. 将函数作用域对象的地址,临时保存到函数作用域链(好友列表)中离函数近的格子中
            在这里插入图片描述
        3. 函数执行过程中:
          函数会按照先局部,在全局的顺序,使用变量
          如果局部有要用的变量,则优先使用局部的变量
          除非函数作用域中没有要用的局部变量,才被迫去下一级作用域链—即此处的window中找变量使用
          在这里插入图片描述

        4. 调用函数后:
          函数会释放函数作用域对象,导致函数作用域对象中的所有局部变量一同释放,所以,所有局部变量都不可重用
          在这里插入图片描述

      4. 示例:判断程序的输出结果

          <script>
            //例子1:
            // var a=10;
            // function fun(){
            //   var a=100;
            //   a++;
            //   console.log(a);
            // }
            // fun();//101
            // console.log(a);//10
            //例子2:
            // var a=10;
            // function fun(){
            //   a=100;
            //   a++;
            //   console.log(a);
            // }
            // fun();//101
            // console.log(a);//101
            //例子3:
            var a=10;
            // 此处a为形参,函数调用完就释放了
            function fun(a){
              a=100;
              a++;
              console.log(a);
            }
            fun();//101
            console.log(a);//10
          </script>
        
  4. 作用域链(scope chain)
    1. 什么是:由多级作用域对象链接起来组成的数据结构,就叫作用域链----即上面的好友列表
      在这里插入图片描述

    2. 保存了一个函数可用的所有变量

    3. 控制着变量的使用顺序:先局部,后全局

3. 闭包

  1. 什么是闭包:

    1. 从作用上来说:既重用一个变量,又保护变量不被污染的一种编程方式
    2. 从本质上来讲:外层函数的作用域对象,被内层函数引用着而无法释放,就形成了闭包对象
  2. 为什么要用闭包:因为js中,全局变量和局部变量都有不可兼得的优缺点

    1. 全局变量:
      优点:可重用;缺点:极易被污染和篡改----开发中几乎禁止使用全局变量
    2. 局部变量:
      优点:因为只能在函数内使用,出了函数就用不了,绝对不会被篡改;缺点:不可重用
  3. 何时用:今后只要想重用一个变量,而且还想保护这个变量不会被别人篡改是,都要用闭包!

  4. 如何用:3步

    1. 用外层函数包裹要保护的变量和内层函数
    2. 外层函数将内层函数对象返回到外部
    3. 想使用内层函数的人,调外层函数,就可获得返回出来的内层函数的对象,再保存在变量中。就可反复使用内层函数对象了
  5. 结果:外层函数与内层函数之间的这个被保护的变量,既可以反复使用,又不会被篡改

  6. 示例:需求定义函数,为小孩保管压岁钱

      <script>
        // 需求:定义一个函数,为小孩保管压岁钱
        // 小孩儿每花一笔钱,可以从总钱数中扣除花的钱,提示还剩xxx
        // 3步
        // 1. 定义一个外层函数,包裹要保护的变量和内层函数
        function parent(){
          var total=1000;
          // 2. 外层函数将内层函数对象返回到函数外部,让外部可用
          // 步用给内层函数起名字
          return function(money){
            total-=money;
            console.log(`花了${money},还剩${total}`)
          }
        }
        // 3. 想用内层函数的人需要调用外层函数,才能获得返回出来的内层函数,保存在变量中,反复使用
        var pay=parent();
    
        // 试图篡改total变量
        total=0;
        console.log(total);//输出0,但是不影响函数内的局部变量total
    
        pay(100);//花了100,还剩900
        pay(100);//花了100,还剩800
        pay(100);//花了100,还剩700
      </script>
    
  7. 闭包原理:笔试题:一句话概括闭包是如何形成的
    外层函数调用后,由于外层函数的作用域对象被内层函数对象引用着,无法释放形成了闭包!
    在这里插入图片描述

  8. 闭包的缺点:比一般的函数多占用一块内存空间----多占外层函数的作用域对象(闭包对象)

  9. 解决方法:当闭包不在使用时,要尽早释放闭包!
    pay=null; ----内存函数对象被释放,导致外层函数的作用域也被释放`

  10. 笔试题:如何判断闭包问题的输出:找2个东西

    1. 找处于外层函数和内层函数之间的变量----要保护的变量

    2. 找外层函数共向外跑出来几个内层函数对象:3种情况

      1. 通过return返回一个内存函数
      2. 通过给全局变量赋值,将内层函数,赋值到外部的变量中
      3. 还是用return或赋值的方式。返回一个数组或一个对象,但是数组或对象中包含函数成员:
        例如:
        var arr=[];
        arr[0]=function(){...};
        arr[1]=function(){...};
        arr[2]=function(){...};
        return arr;//多黄蛋
        
    3. 结论:包裹的多个内层函数共用一个受保护的变量----妈妈一次生的多个孩子共用同一个受保护的变量

    4. 示例:画简图分析闭包程序的输出结果:

        <script>
          function fun(){
            var n=999;
            // js语法规定:任何时候给一个未声明过的变量赋值,不会报错!而是自动在全局创建该变量
            nAdd=function(){n++};
            return function(){
              console.log(n);
            }
          }
          var getN=fun();
          getN();//999
          nAdd();
          getN();//1000
        </script>
      

      在这里插入图片描述

    5. 如果第二次调用外层函数:则会创建全新的一个闭包对象和变量,重新生成一组内层函数对象。与第一次调用外层函数生成的闭包,毫无关系。
      在这里插入图片描述

总结: Function

  1. 创建函数: 3种:
    1. 声明方式: 缺点: 会被声明提前
          function 函数名(形参变量列表){
              函数体;
              return 返回值
          }
      
    2. 赋值方式----函数表达式: 优点: 不会被声明提前
          var 函数名=function (形参变量列表){
              函数体;
              return 返回值
          }
      
    3. 用new: 几乎不用
      var 函数名=new Function("形参1", "形参2",...,"函数体; return 返回值")
  2. 重载:
    1. 何时: 一件事可根据传入的实参值不同,可自动选择执行不同的逻辑
    2. 如何:
      function 一个函数(  不要形参  ){
          //     arguments{   ,  ,   ,    }.length
          //                0  1  2  ...
              //通过判断arguments中的实参值个数或实参内容不同,来决定执行何种逻辑
          }
      
  3. 匿名函数:
    1. 何时: 回调函数 以及 匿名函数自调
    2. 匿名函数自调:
      1. 今后几乎所有js代码都要放在匿名函数自调中。避免使用全局变量
      2. 如何:
        a. 标准写法:
        var 返回值=(function(形参列表){
                        函数体;
                        return 返回值;
                    })(实参值列表);
        
        b. 杀马特写法:
        +function(){ ... }()
        !function(){ ... }{}
        ... ...
  4. 作用域和作用域链:
    1. Js中只有两种作用域:
      1. 全局作用域对象window:
        1). 自动保存所有全局变量
        2). 在程序的任何位置都可访问
      2. 函数作用域对象:
        1. 只在调用函数时创建,调用后自动释放
        2. 函数作用域对象中专门保存函数的局部变量: 2种:
          i. 形参
          ii. 函数内var出的变量
      3. js中和其它语言的差别之一: 没有块级作用域:
        1). 什么是块作用域: 分支结构(if else if else)或循环结构(for while do while switch)的{},在其它编程语言中称为块级作用域
        2). 在其它编程语言中,分支结构或循环结构{}内声明的变量,出了{},不能使用!
        3). Js中,因为没有块级作用域,所以,在分支结构或循环结构{}中声明的变量,出了{},依然可以使用
        4). 示例: 有没有块作用域情况下,程序不同的写法
          <script>
            // js中
            // 如果随机生成一个数是偶数
            if(parseInt(Math.random()*10)%2==0){
              var result="偶数";
            }else{
              var result="奇数";
            }
            console.log(result);
        
            // 在其他有块级作用域的语言中
            var result="";
            if(parseInt(Math.random()*10)%2==0){
              result="偶数";
            }else{
              result="奇数";
            }
            console.log(result);
        
            // js种
            // 求1~100的所有数字的和
            for(var i=1,sum=0;i<=100;i++){
              sum+=i;
            }
            console.log(sum);
            // 在其他有块级作用域的语言中
            var sum=0;
            for(var i=1;i<=100;i++){
              sum+=i;
            }
            console.log(sum);
          </script>
        
    2. 作用域链:
      1. 每个函数对象上其实都保存着一个作用域链列表
      2. 普通函数作用域链中都有两个格子:
        1. 离自己近的格子,不调用函数时,暂时为空,当调用函数时,会引用临时创建的函数作用域对象
        2. 离自己远的格子始终保存window
      3. 作用域链保存着当前函数所有可用的变量
      4. 作用域链控制着变量的使用顺序: 先局部,后全局
  5. 闭包:
    1. 什么是闭包: 外层函数调用后,外层函数的作用域对象,被内层函数引用着无法释放,形成了闭包对象.
    2. 为什么: 全局变量和局部变量都有不可兼得的优缺点:
      1. 全局变量: 优点: 可重用; 缺点: 极易被污染——今后极力避免使用全局变量
      2. 局部变量: 优点: 不会被外部篡改; 缺点: 不可重用
    3. 何时: 希望既可重用一个变量,又想保护该变量不会被随意篡改时
      其实是给一个函数或一组函数保存一个专属的变量
    4. 如何: 3步:
      1. 用外层函数包裹要保护的变量和要使用变量的内层函数
        (注意: 如果内层函数中没有用到外层函数的局部变量,则不会形成闭包)
        function outer(){
                            var i=0;
                            function fun(){
                                console.log(a)
                            }
                        }
                    )
        
      2. 外层函数将内层函数对象返回到外层函数外部
      3. 想使用内层函数的人应该调用外层函数获得内层函数对象,并保存在变量中,可反复使用
    5. 结果: 外层函数的局部变量,只归内层函数专属,其他人无法修改受保护的变量。
    6. 一句话概括闭包如何形成: 外层函数调用后,外层函数的作用域对象,被内层函数引用着,无法释放,形成了闭包。
    7. 闭包缺点: 比一般的函数,多占用一块内存——外层函数的作用域对象
    8. 解决: 将保存内层函数的变量=null
      结果: 释放了内层函数,导致内层函数引用的外层函数作用域对象被释放
    9. 笔试题: 画简图:
      1. 找外层函数和内层函数之间的局部变量。——要保护的变量
      2. 找外层函数妈妈共向外生出了几个内层函数孩子: 在外层函数内3种途径
        1. return function(){ ... }
        2. 全局变量=function(){ ... }
        3. return arr或obj,但是arr或obj中,可能包含函数成员
      3. 结果:
        1. 妈妈一次生出的多个孩子,共享同一个闭包变量,一个函数修改了闭包变量,则另一个函数也受影响----即外层函数内包裹的多个内层函数共用一个闭包变量
        2. 妈妈分两次生的不同的孩子和闭包变量之间,互不影响,毫无关系。-----即多次调用外层函数创建的不同变量之间,闭包变量毫无关系,不共享
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章