JavaScript之閉包

從一個函數的實現說起

話說,我要寫這么一個函數 getCallCounter , 該函數無參數,返回一個數字,第一次調用返回1,第二次調用返回2,... ,以此類推,即

console.log( getCallCounter() );  //輸出為1
console.log( getCallCounter() );  //輸出為2
console.log( getCallCounter() );  //輸出為3

全局變量實現

拿到這個需求之后,我們最簡單的想法就是寫一個全局變量,并初始化為0,每次調用 getCallCounter 時,首先將該全局變量加1,并返回該全局變量,代碼如下:

var g_counter = 0;
function getCallCounter(){
  g_counter += 1;
  return g_counter;
}

上述代碼非常簡單,也達到了要求。但是有一個問題,我們定義了一個 “累贅” -- g_counterg_counter 的定義導致了2個問題:

  • 污染了全局作用域區,因為只有函數 getCallCounter 會用到該變量,所以,該變量應該遵守 "誰要使用,誰來管理" 的原則,而不是放在全局變量區;

  • 任何代碼段都能取得 g_counter ,導致了 getCallCounterg_counter 不能做到完全控制。如果其他代碼修改了 g_counter,那就不能保證每次調用 getCallCounter,我們都能得到一個遞增的數;

以上兩點不足是相互聯系的,正是由于明明應該限定在 getCallCounter 函數中的變量卻放在全局區,才導致上面兩個問題。

因此,我們將定義放到函數中;

改進1

function getCallCounter(){
  var counter = 0;
  counter += 1;
  return counter;
}

改成上述代碼后, counter 受到 getCallCounter 的完全控制,但是,每次調用該函數后,返回都是1;

這是因為函數有自己的作用域,每次完成函數調用,函數中的變量不再被引用,之后將會被 垃圾回收機制 所銷毀,再次調用時,又會創建新的局部變量,因此, getCallCounter 返回的一直是 1;

C語言的實現

為了讓延長函數中局部變量的聲明周期(而不是函數執行完后,內存就被釋放掉),C語言中通過關鍵字 static 為其 "增壽";

int getCallCounter(){
    static int counter = 0;
    counter += 1;
    return counter;
}

這樣,我們每次調用 getCallCounter ,都能夠得到一個遞增的數;

但這畢竟是C語言的實現,那么JavaScript如何實現呢?

解決生命周期問題

我們首先要做的,就是像C語言一樣解決局部變量生命周期過短的問題。

為了保證不被 垃圾回收機制 這個 "劊子手" 所干掉,我們必須始終保持對局部變量的使用,因為 垃圾回收機制 只是針對沒用的數據,而那些還會被使用的數據是不會被當成垃圾的!

所以我們有了下面的代碼:

function f(){
  var counter = 0;
  return function(){
    counter += 1;
    return counter;
  }
}
var getCallCounter = f();

首先,我們定義了一個函數 f ,該函數返回一個函數,因為返回的函數能夠獲得 f 中的局部變量,所以返回的函數能夠得到變量 counter 的值,即 在全局變量作用域中調用一個函數得到了局部作用域中的變量

其次,我們通過變量 getCallCounter 來存放返回的函數,說明返回的函數始終存在變量對該函數引用,因此該返回函數不會被銷毀;而返回的函數又使用了局部變量 counter ,那么解釋器就認為 counter 也是有用的,只要 getCallCounter 不被銷毀,該臨時變量也將不會被銷毀;

閉包的官方定義是:

一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。

Excuse me ? 官方的定義總是會用一些看起來高大上的話讓你覺得牛逼,但是,你就是看不懂。。。

我的理解是:就是一個函數的返回值是一個函數,那么就形成一個函數被另一個函數所 封閉 的情形,而被返回的函數能夠調用定義它的函數中的所有變量,這就形成閉包;

雖然可能不準確,但至少好記憶,好理解。

美化

我們看到前面的函數 f 只被調用了一次,對于這些只調用一次的函數,完全可以寫成 匿名的立即執行的 函數,修改后,代碼為:

var getCallCounter = ( function(){
  var counter = 0;
  return function(){
    counter += 1;
    return counter;
  }
} )();

如此,便可優雅的完成最開始的要求。

完。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容