1.簡單的例子
先從一個經典錯誤談起,頁面上有若干個div, 我們想給它們綁定一個onclick方法,於是有了下面的程式碼
0 1 span>2 3
0 1 span> 2 3
複製程式碼
複製程式碼
複製程式碼
複製程式碼
$(document).ready(function() {
for ( var i = 0; i spans[i].onclick = function() { }
}
}
);
很簡單的功能可是卻偏偏出錯了,每次alert出的值都是4,簡單的修改就好使了
複製程式碼
程式碼如下:
var spans2 = $("#divTest2 span");
$(document).ready(function() { for (var i = 0; i spans2[i]. @ alert(num); }
2.內部函數
讓我們從一些基礎的知識談起,首先了解一下內部函數。內部函數就是定義在另一個函數中的函數。例如:
複製程式碼
程式碼如下:
}innerFn就是一個被包在outerFn作用域中的內部函數。這意味著,在outerFn內部呼叫innerFn是有效的,而在outerFn外部呼叫innerFn則是無效的。下面程式碼會導致一個JavaScript錯誤:
複製碼
程式碼如下:
function outerFn() {"); function innerFn() { } } innerFn();
不過在outerFn內部呼叫innerFn,則可以成功運行:
function outerFn() {");
function innerFn() {
}
innerFn();
}
outerFn();
2.1偉大的逃脫
JavaScript允許開發人員像傳遞任何類型的資料一樣傳遞函數,也就是說,JavaScript中的內部函數能夠逃脫定義他們的外部函數。
逃脫的方式有很多種,例如可以將內部函數指定給一個全域變數:
var globalVar;
function outerFn() {
function innerFn() {
document. write("Inner function
");
}
outerFn();
globalVar();
呼叫outerFn時會修改全域變數globalVar,這時候它的引用變成innerFn,此後呼叫globalVar和呼叫innerFn一樣。這時在outerFn外部直接呼叫innerFn仍然會導致錯誤,這是因為內部函數雖然透過把引用保存在全域變數中實現了逃脫,但這個函數的名字依然只存在於outerFn的作用域中。
也可以透過在父函數的回傳值來獲得內部函數參考
複製碼
程式碼如下:function outerFn() {"); function innerFn() {
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
這裡並沒有在全局透過呼叫outerFn能夠獲得這個引用,而且這個引用可以可以保存在變數中。
這種即使離開函數作用域的情況下仍然能夠透過引用呼叫內部函數的事實,意味著只要存在呼叫內部函數的可能,JavaScript就需要保留被引用的函數。而且JavaScript在運行時需要追蹤引用這個內部函數的所有變量,直到最後一個變數廢棄,JavaScript的垃圾收集器才能釋放對應的記憶體空間(紅色部分是理解閉包的關鍵)。
說了半天總算和閉包有關係了,閉包是指有權限訪問另一個函數作用域的變數的函數,創建閉包的常見方式就是在一個函數內部創建另一個函數,就是我們上面說的內部函數,所以剛才說的不是廢話,也是閉包相關的^_^
1.2變數的作用域
內部函數也可以有自己的變量,這些變數都被限制在內部函數的作用域:
複製程式碼
複製程式碼 程式碼如下:
function outerFn() {
document.write("Outer function
");
in var innerVar = 0;
innerVar ;
document.write("Inner functiont");
}
return innerFn;
fnRef = outerFn();
fnRef();
fnRef();
fnRef2();
fnRef2();
每當透過引用或其它方式呼叫這個內部函數時,就會建立一個新的innerVar變量,然後加1,最後顯示
複製程式碼
程式碼如下:Outer functionInp >Inner function innerVar = 1Outer function
Inner function innerVar = 1Inner function innerVar = 1
複製程式碼
程式碼如下:
var fnRef = outerFn(); fnRef();
fnRef( );
var fnRef2 = outerFn();
fnRef2();
fnRef2();
fnRef2();
這個全域變數的值:
複製程式碼
程式碼如下:
Outer functionInner function globalVar = 2
Outer functionInner function globalVar = 3Inner function globalVar = 4
複製程式碼
程式碼如下:
function outerFn() {
var outerVar = 0;
function innerFn() {
outerVar ;
document.write("Inner functiont");
> }
return innerFn;
fnRef = outerFn();
fnRef();
fnRef();
fnRef2();
fnRef2();
這次結果很有趣,也許或出乎我們的意料
複製程式碼
程式碼如下:Outer functionInner function outerVar = 2Outer function
Inner function outerVar = 1
Inner function outerVar = 2
Inner function outerVar = 2
Inner 🎜>每個引用調用innerFn都會獨立的遞增outerVar。也就是說第二次呼叫outerFn沒有繼續沿用outerVar的值,而是在第二次函數呼叫的作用域建立並綁定了一個新的outerVar實例,兩個計數器完全無關。
當內部函數在定義它的作用域的外部被引用時,就創建了該內部函數的一個閉包。這種情況下我們稱既不是內部函數局部變量,也不是其參數的變數為自由變量,稱外部函數的呼叫環境為封閉閉包的環境。從本質上講,如果內部函數引用了位於外部函數中的變量,則相當於授權該變數能夠被延遲使用。因此,當外部函數呼叫完成後,這些變數的記憶體不會被釋放(最後的值會保存),閉包仍然需要使用它們。
3.閉包之間的互動
當存在多個內部函數時,很可能會出現意料之外的閉包。我們定義一個遞增函數,這個函數的增量為2
複製程式碼
程式碼如下: