JavaScript之閉包

從一個函數(shù)的實現(xiàn)說起

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

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

全局變量實現(xiàn)

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

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

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

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

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

以上兩點不足是相互聯(lián)系的,正是由于明明應(yīng)該限定在 getCallCounter 函數(shù)中的變量卻放在全局區(qū),才導(dǎo)致上面兩個問題。

因此,我們將定義放到函數(shù)中;

改進1

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

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

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

C語言的實現(xiàn)

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

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

這樣,我們每次調(diào)用 getCallCounter ,都能夠得到一個遞增的數(shù);

但這畢竟是C語言的實現(xiàn),那么JavaScript如何實現(xiàn)呢?

解決生命周期問題

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

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

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

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

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

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

閉包的官方定義是:

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

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

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

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

美化

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

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

如此,便可優(yōu)雅的完成最開始的要求。

完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容