1. 程式人生 > 其它 >【技術雜貨鋪】JS中的閉包是個什麼東西

【技術雜貨鋪】JS中的閉包是個什麼東西

  我自己想要了解到閉包這個概念是因為在上一篇隨筆,介紹防抖和節流的時候,有一段程式碼看得我有點犯迷糊,如下

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

  按照我之前的瞭解,每次呼叫這個debounce防抖函式的時候,timer都要被重新賦值為null,這怎麼起得到定時器的作用呢?後來瞭解到這是因為閉包的存在,所以timer這個變數在記憶體一直儲存下來,而不是重新清空。好了,下面正式介紹閉包就能知道我在嘮叨什麼了。

  要說清楚這個問題,我覺得要先從基礎說起,並且清楚裡面的一些細節,才能理解清楚。js的變數分為全域性變數和區域性變數。

  全域性變數:可以在任意位置訪問的變數就是全域性變數。例如下面的age。

var age = 10;
function a(){
    console.log(age);  //輸出為10  
}

  區域性變數:函式中用var定義的變數,只能在函式中訪問這個變數,函式外部訪問不了。

function a(){
   var age = 10;
}
a();
console.log(age); //Uncaught ReferenceError: age is not defined

  OK,上面這些大家應該都是很熟悉的了,那麼有兩個注意點要注意,

  1.在函式中如果不適用var定義變數那麼js引擎會自動將該變數變為全域性變數。

  2.全域性變數自建立起就一直儲存在記憶體,直到你關閉頁面,而區域性變數是當其所在的函式執行完後就會銷燬,假如再次呼叫該函式,會再次重新建立這個變數,即執行完就銷燬,回到最初的狀態。

  再來說說函式,一個函式可以套很多別的函式,函式裡面的子函式可以訪問到它上級函式定義的變數,這個上級可以像冒泡一樣,一直往上找,如果找到全域性變數都找不到,那麼就會報錯。

  閉包

  下面要通過一個例子來引入閉包

function a(){
    var
count = 0; console.log(++count); } a(); //輸出1 a(); //還是輸出1

  上面的程式碼輸出了兩個1,是因為上面的區域性變數在函式執行完一次後就會銷燬,不會儲存在記憶體,再次呼叫時會重新建立那個變數。那麼問題來了,我們怎樣做才能確保第一次的建立的變數不會被銷燬呢,就是說,如果讓第二次呼叫該函式時,輸出的是2,不是1呢?這就需要我們的閉包出來了。

  Warning:JavaScript中有回收機制,函式沒有被引用執行完以後這個函式的作用域就會被銷燬,如果一個函式被其他變數引用(重要),這個函式的作用域將不會被銷燬,(簡單來說就是函式裡面的變數會被儲存下來,你可以理解成全域性變數。)

  好了,下面請出我們的閉包:

function a(){
    var count = 0;
    function b(){
        count ++;
        console.log(count);
    }
    return b;
}
var ab = a();
ab(); //輸出1
ab(); //輸出2

  這裡可以看到,裡面的變數count的值並沒有重新計算,因為函式a被外部的變數ab引用,所以變數count沒有被回收。

  如果某個函式被它的父函式之外的一個變數引用,就形成了一個閉包。

  還有一種更為常用的閉包寫法:

var ab = (function(){
    var count = 0;
    function b(){
        count++;
        console.log(count);
    }
    return b;
})();

bi(); //1
bi(); //2
bi(); //3

  執行過程分析:首先把一個自執行函式賦值給了ab,這個自執行函式執行完後ab的值就變為了

function b(){
        count++;
        console.log(count);
    }

  因為我們在上面的程式碼return回去了b,表示將b賦值給了ab,然後因為這個自執行函式被ab引用所以裡面的count變數並沒有因為這個自執行函式執行完而銷燬,而是儲存到了記憶體中,所以多次列印會累加。