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. 媽媽分兩次生的不同的孩子和閉包變量之間,互不影響,毫無關係。-----即多次調用外層函數創建的不同變量之間,閉包變量毫無關係,不共享
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章