簡單理解 JavaScript 閉包問題

從我16年開始接觸前端,知道閉包這個詞,已通過去兩年了。這兩年裏,閉包這個概念我在不少地方瞭解過,卻實在沒有真的理解,長此以往,變成了一塊心病。這不,趁着如今項目告一段落的時間,我又開始折騰它了,在網易雲課堂上找了些資源來看。哈哈,多是由於已經作了些項目的緣故,此次終於能理解它究竟是個什麼玩意兒了。javascript

概念

閉包,在《javascripts高級程序設計》裏面是這樣介紹的:閉包是指有權訪問另外一個做用域中的變量的函數。額。。這句話我之前看過不少遍,但依然不是很懂,只知道它是跟做用域有關。如今我知道了,若是這句話換成:但凡是內部的函數被保存到了外部,一定生成閉包。這樣就容易理解多了不是。
咱們如下面的這個代碼塊爲例:前端

例子解釋

function a() {
    const num = 100;
    function b () {
        num++;
        console.log(num);
    }
     return b;
}
const demo = a();
demo();
demo();

咱們先執行上述代碼,看看結果是什麼:java

爲何是這樣呢?數組

a()執行的結果是返回b,因此demo指的是b,則demo()指的是b();也就是說原先在a裏面的b,在b的外部執行;
咱們已經知道了做用域和做用域鏈的概念,上面代碼的做用域是這樣的:緩存

a執行完,返回了b,此時的b只是聲明,但還沒調用,因此沒有造成本身的AO,但做用域鏈和 a doing 時是同樣的,因此雖然 a() 的做用域被銷燬了,可是相同的一份卻被b保存到了外面。這也就是內部函數被保存到了外面造成閉包的本質。這樣也不難理解爲何上述代碼打印出來的值是那樣的了:閉包

  • 執行第一個demo()時,也便是執行 b(),因爲b保存了a的做用域鏈,因此也能夠訪問到 num ,執行 b() 後,加一;
  • 那爲何第二次執行 demo(),打印出的值仍是有自增了呢?這是由於操做的都是保存在 b 裏的 num ,雖然每次調用 demo() 都會造成新的做用域鏈,可是num,倒是每次從上一次的做用域鏈直接 copy到當前做用域鏈中的。

這樣造成的閉包雖然可使外部能夠訪問到內部的函數,可是致使了原有的做用域鏈不釋放,會形成內存泄漏。(內存泄漏的意思就是佔用內存,可用內存資源變少了)。因此若是不是特殊須要,應儘可能防止這種狀況發生。模塊化

而且,做用域鏈的配置機制引出了一個值得注意的反作用:即閉包只取得包含函數中任何變量最後一個值,好比下面這個例子:函數

function createFunctions() {
     var result = [];
     for(let i = 0; i< 10; i++) {
         result[i] = function() {
             console.log(i);
          }
       }
    return result;
 }
var myArr = createFunctions();
for(var j = 0; j < 10; j++) {
       myArr[j]();
 }

這個函數會返回一個函數數組,表面上看,彷佛每一個函數都應該有本身的索引值,即會返回:0,1,2...9;但實際上,每一個函數都會返回10;這是由於在createFunctions()執行時,for循環跳出的條件是i=10;因此函數返回後,i的值是10 ;而每一個result的做用域鏈中都保存這createFunctions()的AO,因此他們引用的都是createFunctions()的i值,因此每一個函數內部i的值都是10;
這樣,咱們能夠建立一個當即執行函數強制讓閉包的行爲符合預期:spa

function createFunctions() {
     var result = [];
     for(var i = 0; i < 10; i++) {
           (function(j) {
             result[i] = function() {
                document.write(j + " ");
                }
          }(i));
     }
    return result;
}
var myArr = createFunctions();
for(var j = 0; j < 10; j++) {
       myArr[j]();
 }

典型應用

下面看看幾個用到閉包的典型例子:設計

  • 實現共有變量

    如累加器:調用多少次,累加多少次,用閉包更加模塊化

function add() {
            var count = 0;
            function demo() {
                count++;
                console.log(count);
            }
            return demo;
        }
        var counter = add();
        counter();//1
        counter();//2
        counter();//3
  • 實現緩存

如eater: eat和push保存的都是eater的AO;,因此eat中food改變後。其實是eater變了,因此也會影響push;

function eater() {
            var food = '';
            var obj = {
                eat: function() {
                    console.log('eating' + food);
                    food = '';
                },
                push: function(myFood) {
                    food = myFood;
                } 
            }
            return obj;// 至關於返回裏面的eat和push操做food;
        }
        var eater1 = eater();
        eater1.push('banana');
        eater1.eat();
相關文章
相關標籤/搜索