JavaScript閉包,你理解嗎?
簡言之,理解很多問題的關鍵是:閉包只有在調用的時候才進行解析。
維基百科上對閉包的定義是: Closure (also lexical closure or function closure) is a function together with a referencing environment for the non-local variablesof that function.用我拙略的語言翻譯過來就是:閉包(又稱“詞法閉包”或“函數閉包”)是一個包含了非本地變量引用環境的函數。
閉包其實就是一個函數;如果一個函數訪問了它的外部變量,那么它就是一個閉包。一個典型的例子就是全局變量的使用。所以從技術上來講,在Javascript中,每個function都是閉包,因為它總是能訪問在它外部定義的變量。
示例:
首先來看一個簡單的例子:
function say667() { // Local variable that ends up within closure var num = 666; var sayAlert = function() { alert(num); } num++; return sayAlert; } var sayAlert = say667(); sayAlert()
執行結果應該彈出667而不是666,這個應該很好理解。再來看一個容易迷惑的經典例子:
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { var item = 'item' + list[i]; result.push( function() {alert(item + ' ' + list[i])} ); } return result; } function testList() { var fnlist = buildList([1,2,3]); // using j only to help prevent confusion - could use i for (var j = 0; j < fnlist.length; j++) { fnlist[j](); } }
testList的執行結果是彈出item3 undefined窗口三次。因為這三個閉包是在同一個外部函數中定義的,item的值為最后計算的結果,但是當i跳出循環時i值為3,所以list[3]的結果為undefined.
將引用變為拷貝?
理解問題的關鍵是,Javascript是一門解釋性的語言,一個函數內部定義的另一個函數(即閉包)只有在調用的時候才進行解析。buildList函數中定義閉包時,使用了參數"list"以及內部變量"i"的引用,而不是拷貝。因此只有當閉包執行時,也就是在testList函數中調用時,才會開始引用list和i的值并輸出;而此時i的值為4,結果可想而知了!
為了達到預期的效果,我們來改造一下buildList函數,而改造的關鍵是在每次循環中創建變量i的拷貝,也就是將引用變為拷貝!?一種簡單的方法就是使用自執行的“匿名函數”來對閉包進行包裹:
function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { (function(r){ var item = 'item' + list[r]; result.push( function() {alert(item + ' ' + list[r])} ); })(i); } return result; }
這樣,在函數buildList執行的時候,匿名函數會立即執行,并把i作為參數;此時匿名函數內部的變量r相當于有了i的一個拷貝,而r的值是不會被外部的循環改變的。因此函數testList的執行結果是分別彈出“item1 1”、“item2 2”、“item3 3”。?
你理解了嗎??
要小心的是,在Javascript函數參數傳遞的時候,只有基本類型的參數會被拷貝,對象類型的參數傳遞的是引用。因此,如果給匿名函數傳遞對象類型的參數時(沒有人會這么做吧!),要小心出現意外的情況;舉個變態的例子:
function buildList(list) { var result = []; var obj = {}; for (obj.i = 0; obj.i < list.length; obj.i++) { (function(r){ var item = 'item' + list[r.i]; result.push( function() {alert(item + ' ' + list[r.i])} ); })(obj); } return result; }
函數testList的執行結果是什么呢?是分別彈出“item1 undefined”、“item2 undefined”、“item3 undefined”??窗口,跟前面兩種寫法的結果都不一樣。原因是匿名函數立即執行后,其內部變量item被正確賦值,等到testList函數運行時,閉包中引用的r.i其實就是obj對象的i變量,它的值當然是3,結果就可想而知了。
閉包,你理解了嗎??