JS高級:閉包

1 如何產生閉包?

當一個嵌套的內部(子)函數引用了嵌套的外部(父)函數的變量(函數)時, 就產生了閉包(closure)javascript

2 閉包究竟是什麼?

使用chrome調試查看java

理解一: 閉包是嵌套的內部函數chrome

理解二: 包含被引用變量(函數)的對象閉包

注意: 閉包存在於嵌套的內部函數中異步

3 產生閉包的條件?

函數嵌套函數

內部函數引用了外部函數的數據(變量/函數)性能

4 常見的閉包使用形式?

4.1 將函數做爲另外一個函數的返回值

// 1. 將函數做爲另外一個函數的返回值
    function fn1() {
        var num = 10;
        function fn2() {
            num++;
            console.log(num);
        }
        return fn2;
    }

    // 經過全局變量引用, 保住了內部函數fn2的命
    var f = fn1();
    f(); // 11 在外部函數執行完成後, 還能夠執行內部函數

    f(); //

4.2 將函數的形參做爲實參傳遞給另外一個函數調用

// 2. 將函數的形參做爲實參傳遞給另外一個函數調用    
     function logMsgDelay(msg, time) {
       setTimeout(function () {
         console.log(msg);
       }, time)
     }

5 閉包的做用分析

  • 使用函數內部的變量在函數執行完後, 仍然存活在內存中(延長了局部變量的生命週期)
  • 讓函數外部能夠操做(讀寫)到函數內部的數據(變量/函數)

6 理解閉包解決同步和異步

封閉做用域又稱值爲封閉空間,還有一個暱稱叫小閉包,以及匿名函數自調。this

<button>按鈕1</button>
<button>按鈕2</button>
<button>按鈕3</button>

    /*
    封閉做用域又稱值爲封閉空間,還有一個暱稱叫小閉包,以及匿名函數自調。
    寫法:
    (function(){})();
    ;(function(){})();
    +(function(){})();
    -(function(){})();
  */

    var btns = document.getElementsByTagName('button');
    /*
        藉助小閉包, 把每次循環的i值都封閉起來
    */
    for (var i = 0; i < btns.length; i++) {
        console.log('全局的i:' + i);
        (function (i) {
            console.log('局部的i:' + i);
            var btn = btns[i];
            btn.onclick = function () {
                alert('第' + (i + 1) + '個')
            }
        })(i);
    }

7 模塊封裝(封裝全局變量)

做用域鏈條
JS中有不少做用域, 好比: 全局做用域 和 局部做用域指針

  1. 凡是存在做用域的地方必定有做用域鏈條, 變量的查找都是沿着這條鏈條自內而外的;
  2. 尋找變量都是遞歸遍歷尋找, 當前做用域找不到, 就跳到上一個做用域遍歷尋找, 直至頂層;
  3. 做用域鏈條太長, 會影響程序運行效率

把一些不須要暴露在全局的變量封裝成"私有變量"調試

7.1 私有模塊封裝

MyTool1.js

function myTool() {
    // 1.私有數據
    var money =  1000;
    // 2. 操做數據的函數
    function get() {
        money++;
        console.log('賺了一筆錢, 總資產: ' + money + '元');
    }
    function send() {
        money--;
        console.log('花了一筆錢, 總資產: '+ money + '元');
    }
    //向外暴露對象(給外部使用的方法)
    return {
        'get': get,
        'send': send
    }
}

調用

<script type="text/javascript" src="js/MyTool1.js"></script>
    <script type="text/javascript">
        var tool = myTool();
        tool.get();
        tool.send();
    </script>

7.2 全局模塊(window)封裝

MyTool2.js

;(function (window) {
    // 1.私有數據
    var money =  1000;
    // 2. 操做數據的函數
    function get() {
        money++;
        console.log('賺了一筆錢, 總資產: ' + money + '元');
    }
    function send() {
        money--;
        console.log('花了一筆錢, 總資產: '+ money + '元');
    }

    //向外暴露對象(給外部使用的方法)
    window.myTool = {
        get: get,
        send: send
    }
})(window);

/*
   性能考慮, 做用域鏈條是遞歸查找對象的
   壓縮考慮, a,b,c,...
*/

調用

<script type="text/javascript" src="js/MyTool2.js"></script>
<script type="text/javascript">
    myTool.get();
    myTool.send();
</script>

8 場景應用

8.1 高級排他

2個for循環,改成設置1個,根據下標清除

// window.onload = function () {
    //     var allLis = document.getElementsByTagName('li');
    //     for(var i=0; i<allLis.length; i++){
    //         var li = allLis[i];
    //         li.onmouseover = function () {
    //             for(var j=0; j<allLis.length; j++){
    //                  allLis[j].className = '';
    //             }
    //             this.className = 'current';
    //         }
    //     }
    // }


    window.onload = function () {
        var allLis = document.getElementsByTagName('li');
        // 記錄移動前選中li對應的索引
        var preSelectLiIndex = 0;
        for(var i=0; i<allLis.length; i++){
            (function (i) {
                var li = allLis[i];
                li.onmouseover = function () {
                    // 清除
                    allLis[preSelectLiIndex].className = '';
                    // 設置
                    this.className = 'current';
                    // 賦值
                    preSelectLiIndex = i;
                }
            })(i);
        }
    }

8.2 函數節流

前面的timer做爲全局變量,window指針指向它,若是有不少,影響性能。

/*
    var timer = null;
    window.onresize = function () {
        clearTimeout(timer);
        timer = setTimeout(function () {
            console.log('輸出的內容!!!!');
        }, 200);
    }
    */


   window.onresize = throttle(function () {
       console.log('你們好!!!');
   }, 200);

    function throttle(fn, delay) {
        var timer = null;
        return function () {
            clearTimeout(timer);
            timer = setTimeout(fn, delay);
        }
    }

9 閉包的缺點

  1. 缺點
    函數執行完後, 函數內的局部變量沒有釋放, 佔用內存時間會變長
    容易形成內存泄露
  2. 解決
    及時釋放
function fn1() {
    var arr = new Array[999999999];
    function fn2() {
      console.log(arr.length)
    }
    return fn2
  }
  var f = fn1();
  f();

  f = null //讓內部函數成爲垃圾對象-->回收閉包

10 內存管理

10.1 內存溢出

一種程序運行出現的錯誤
當程序運行須要的內存超過了剩餘的內存時, 就出拋出內存溢出的錯誤

var arrObj = {};
    for (var i = 0; i < 10000; i++) {
        arrObj[i] = new Array(9999999999999);
        console.log(arrObj);
    }

10.2 內存泄露

佔用的內存沒有及時釋放
內存泄露積累多了就容易致使內存溢出
常見的內存泄露:
1. 佔用內存很大的全局變量
2. 沒有及時清理的計時器/定時器
3. 閉包

// 2. 內存泄露
    // 2.1 佔用內存很大的全局變量
    /*
    var num = new Array(9999999999999);
    console.log(num);
    */

    // 2.2 沒有及時清理的計時器或回調函數
    /*
    var intervalId = setInterval(function () { //啓動循環定時器後不清理
       console.log('----')
     }, 1000);
     clearInterval(intervalId);
     */

    // 2.3 閉包
    /*function fn1() {
      var num = 111;
      function fn2() {
        console.log(num--);
      }
      return fn2
    }
    var f = fn1();
    f();*/

    // f = null
相關文章
相關標籤/搜索