從一個函數(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)致了getCallCounter對g_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)雅的完成最開始的要求。
完。