對於希望在javascript技術中提高的人群來說,閉包肯定時常是一個令人感覺神秘的技術。早先有人說javaScript中的閉包可能會引發javaScript記憶體管理的複雜度,也許會出現記憶體泄露,所以不建議用閉包。不過jQuery很好的證明了閉包非常好用,C#的Linq也證明的閉包技術的重要性,所以花一點點時間來理解下閉包還是很值得的,再說了,以下的內容不過就是一杯茶的時間而已。
先給出一個閉包的定義:在電腦科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函數。這個被引用的自由變數將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。
以上定義中非常重要的是
閉包關聯到:函數和變數
閉包鎖定了:函數和變數的環境關係
常有人在說閉包前會給出類似以下案例,說是javaScript的特殊性,函數中訪問全域變數,這個我一直不太明白,一個函數訪問它所在環境中的全域變數很特殊很奇怪嗎?
var a = 10;function fun1() { alert(a);}fun1();
而且就算有以下代碼我也絲毫不感覺有任何問題
var a = 10;function fun2() { b = 100;// 全域變數}function fun1() { alert(a); alert(b);}fun2();fun1();
真正值得你考量的是以下代碼
function fun1(x) { var a = x; function fun2() { return a; } return fun2;}alert(fun1(100)()); //100alert(fun1(9)()); //9
如果從結果來看,你也許同樣不屑一顧,返回的值很明確的嘛。但是你仔細想想,問題就不簡單了
函數fun1的結果是返回了一個fun2的函數。從代碼來講,fun1()是調用了fun1的執行,並且得到了fun2的函數,fun1()()就是說是執行fun2()了!如果你還是感覺正常的話,要麼說明你已經理解閉包了,要麼就是你忽略了一個重要的事實!
調用fun1()後,fun1所佔用的記憶體應該已經釋放了,fun1函數中的所有變數都將釋放!!!對不?
function sum(x, y) { var a = x, b = y; return a + b;}alert(sum(9, 5));//14
上面的代碼,當我完成sum(9,5)之後,sum函數肯定被釋放了,a和b將不存在是不?如果你還是感覺不是很明晰的話,那麼看下如下的分解動作吧。
function fun1(x) { var a = x; function fun2() { return a; } return fun2;}var fun3 = fun1(10);var fun4 = fun1(9);alert(fun4()); //9alert(fun3()); //10
看看第一次的fun1將x的值賦值了10,第二次的fun1將x的值賦值為9,而且我們先執行了fun4,要求返還的值是9,再次執行fun3的得到的是10!!!說明什麼呢?說明當fun2被建立的時候,它將它所需要用到的變數鎖住 了,或者不這麼誇張的說是記憶住了。
閉包就是函數在建立自己的時候,將需要用的變數鎖住或說記憶住。
這裡的函數建立不是指函式宣告,而且指函數運算式被啟用的時候,匿名函數運算式的啟用有:call就是()調用,()分組,還有就是return的時候。
看看如下案例
var dofun = [];for (var i = 0; i < 10; i++) { dofun[i] = function() { return i; }}for (var j = 0; j < 10; j++) { alert(dofun[j]()); //全部返還10}
很多人在網上用過這個案例來說明閉包的特性,我需要很嚴肅的聲明兩個問題
1 這個案例可以說明閉包
2 這個案例還有更神奇的特性說明
先說下閉包,因為當這個返回的匿名函數只有在
dofun[j]()
的時候才被啟用,這個時候i的值是10,所以這個函數被返回了10,要解決這個問題,可以要求函數在i是各種值的時候被啟用,怎麼啟用?return
var dofun = [];for (var i = 0; i < 10; i++) { dofun[i] = (function(k) { return function() { return k; } }(i));}for (var j = 0; j < 10; j++) { alert(dofun[j]()); //全部返還10}
這個處理中很有意思的是,我需要一個k來幫忙,為什麼呢?
那就是我剛才說的第2點,先看下如下很令人無語的代碼
var dofun = [];for (var i = 0; i < 10; i++) { dofun[i] = function() { return i; }}for (var i = 0; i < 10; i++) { alert(dofun[i]());}
咋一看,你估計會暴跳起來,這不是耍人嘛,結果我們已經知道了,都是10!!!!錯!!!!!!彈出的分別是0 1 2 3 4 5 6 7 8 9!!!
不相信你就測試一下看看。你仔細看看代碼不同在哪裡?下面的for用了變數是i,這就是不同所在,也是閉包的關鍵所在!
函數是不是值得來鎖定一個變數,是看該變數在調用這個函數的時候,是不是能在上下文範圍中找到這個變數,如果無法在調用時找到這個變數,內建函式就會鎖住它,否則就不會鎖住,至少表面上是這樣的。
現在我們再把目光移到最初的那個案例
function fun1(x) { a = x; function fun2() { return a; } return fun2;}var fun3 = fun1(10);var fun4 = fun1(9);alert(fun4()); //9a = 999;alert(fun3()); //999
fun3()的結果是999的原因倒不是在乎現在a是全域變數,而是現在fun3在執行的時候能在上下文範圍中找到a了,全域變數不過是湊齊罷了。