1.2變量的作用域
內部函數也可以有自己的變量,這些變量都被限制在內部函數的作用域中:
function outerFn() {
document.write(“Outer function《br/》”);
function innerFn() {
var innerVar = 0;
innerVar++;
document.write(“Inner function\t”);
document.write(“innerVar = ”+innerVar+“《br/》”);
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();
每當通過引用或其它方式調用這個內部函數時,就會創建一個新的innerVar變量,然后加1,最后顯示
Outer function
Inner function innerVar = 1
Inner function innerVar = 1
Outer function
Inner function innerVar = 1
Inner function innerVar = 1
內部函數也可以像其他函數一樣引用全局變量:
var globalVar = 0;
function outerFn() {
document.write(“Outer function《br/》”);
function innerFn() {
globalVar++;
document.write(“Inner function\t”);
document.write(“globalVar = ” + globalVar + “《br/》”);
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();
現在每次調用內部函數都會持續地遞增這個全局變量的值:
Outer function
Inner function globalVar = 1
Inner function globalVar = 2
Outer function
Inner function globalVar = 3
Inner function globalVar = 4
但是如果這個變量是父函數的局部變量又會怎樣呢?因為內部函數會引用到父函數的作用域(有興趣可以了解一下作用域鏈和活動對象的知識),內部函數也可以引用到這些變量
function outerFn() {
var outerVar = 0;
document.write(“Outer function《br/》”);
function innerFn() {
outerVar++;
document.write(“Inner function\t”);
document.write(“outerVar = ” + outerVar + “《br/》”);
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();
這一次結果非常有意思,也許或出乎我們的意料
Outer function
Inner function outerVar = 1
Inner function outerVar = 2
Outer function
Inner function outerVar = 1
Inner function outerVar = 2
我們看到的是前面兩種情況合成的效果,通過每個引用調用innerFn都會獨立的遞增outerVar。也就是說第二次調用outerFn沒有繼續沿用outerVar的值,而是在第二次函數調用的作用域創建并綁定了一個一個新的outerVar實例,兩個計數器完全無關。
當內部函數在定義它的作用域的外部被引用時,就創建了該內部函數的一個閉包。這種情況下我們稱既不是內部函數局部變量,也不是其參數的變量為自由變量,稱外部函數的調用環境為封閉閉包的環境。從本質上講,如果內部函數引用了位于外部函數中的變量,相當于授權該變量能夠被延遲使用。因此,當外部函數調用完成后,這些變量的內存不會被釋放(最后的值會保存),閉包仍然需要使用它們。
3.閉包之間的交互
當存在多個內部函數時,很可能出現意料之外的閉包。我們定義一個遞增函數,這個函數的增量為2
function outerFn() {
var outerVar = 0;
document.write(“Outer function《br/》”);
function innerFn1() {
outerVar++;
document.write(“Inner function 1\t”);
document.write(“outerVar = ” + outerVar + “《br/》”);
}
function innerFn2() {
outerVar += 2;
document.write(“Inner function 2\t”);
document.write(“outerVar = ” + outerVar + “《br/》”);
}
return { “fn1”: innerFn1, “fn2”: innerFn2 };
}
var fnRef = outerFn();
fnRef.fn1();
fnRef.fn2();
fnRef.fn1();
var fnRef2 = outerFn();
fnRef2.fn1();
fnRef2.fn2();
fnRef2.fn1();
我們映射返回兩個內部函數的引用,可以通過返回的引用調用任一個內部函數,結果:
Outer function
Inner function 1 outerVar = 1
Inner function 2 outerVar = 3
Inner function 1 outerVar = 4
Outer function
Inner function 1 outerVar = 1
Inner function 2 outerVar = 3
Inner function 1 outerVar = 4
innerFn1和innerFn2引用了同一個局部變量,因此他們共享一個封閉環境。當innerFn1為outerVar遞增一時,久違innerFn2設置了outerVar的新的起點值,反之亦然。我們也看到對outerFn的后續調用還會創建這些閉包的新實例,同時也會創建新的封閉環境,本質上是創建了一個新對象,自由變量就是這個對象的實例變量,而閉包就是這個對象的實例方法,而且這些變量也是私有的,因為不能在封裝它們的作用域外部直接引用這些變量,從而確保了了面向對象數據的專有性。
3.解惑
現在我們可以回頭看看開頭寫的例子就很容易明白為什么第一種寫法每次都會alert 4了。
for (var i = 0; i 《 spans.length; i++) {
spans[i].onclick = function() {
alert(i);
}
}
上面代碼在頁面加載后就會執行,當i的值為4的時候,判斷條件不成立,for循環執行完畢,但是因為每個span的onclick方法這時候為內部函數,所以i被閉包引用,內存不能被銷毀,i的值會一直保持4,直到程序改變它或者所有的onclick函數銷毀(主動把函數賦為null或者頁面卸載)時才會被回收。這樣每次我們點擊span的時候,onclick函數會查找i的值(作用域鏈是引用方式),一查等于4,然后就alert給我們了。而第二種方式是使用了一個立即執行的函數又創建了一層閉包,函數聲明放在括號內就變成了表達式,后面再加上括號括號就是調用了,這時候把i當參數傳入,函數立即執行,num保存每次i的值。
一、什么是閉包?“官方”的解釋是:所謂“閉包”,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。相信很少有人能直接看懂這句話,因為他描述的太學術。我想用如何在Javascript中創建一個閉包來告訴你什么是閉包,因為跳過閉包的創建過程直接理解閉包的定義是非常困難的??聪旅孢@段代碼: function a(){ var i=0; function b(){ alert(++i); } return b;}var c = a();c();
評論
查看更多