最常見的:
用匿名函數:
1 var foo = function () { 2 alert('hi, js'); 3 } 4 foo();
改裝一下,給調用的foo()加個括號:
1 var foo = function () { 2 alert('hi, js'); 3 } 4 (foo)();
干脆,連foo的定義也省掉------把foo賦值語句的等號右邊的東東、直接替換掉剛才括起來的foo:
1 (function () { 2 alert('hi, js'); 3 })();
先看常用的函數:
閉包的例子
一句話描述:
- 閉包是函數的局部變量,在函數return之后,仍然有值, 或者
- 閉包是stack-frame,在函數return的時候,它不會被釋放。(就好像'stack-frame'是內存分配、而非處於堆棧!)
下面的代碼返回一個function的引用:
1 function sayHello2(name) { 2 var text = 'Hello ' + name; // local variable 3 var sayAlert = function() { alert(text); } 4 return sayAlert; 5 } 6 var say2 = sayHello2('Jane'); 7 say2(); //hello Jane
多數JavaScript程序員能夠理解上面代碼的函數引用如何返回給變量。請在學習閉包之前理解它。C程序員把函數看做返回的一個函數指針,變量sayAlert和say2分別是函數指針。
C函數指針和JavaScript的函數引用有着本質的不同。在JavaScript,函數引用變量既是一個函數指針,又是一個隱藏的閉包指針。
上面代碼擁有閉包,因為函數體內又聲明了一個匿名函數 function() { alert(text); } ,參看例子中的sayHello2()。如果你在另一個函數內使用function關鍵字,閉包就產生了。
在C和其他多數語言,當函數return之后,stack-frame就被銷毀了,所有的局部變量也就不能訪問了。
在JavaScript中,如果你在函數里聲明了函數,在你調用的函數renturn之后,局部變量仍然可以訪問。請注意上面的例子,我們調用了變量text,它是函數sayHello2的局部變量。
Example 3
這個例子表明局部變量不是拷貝傳遞,而是引用傳遞。在外層函數退出時,它把stack-frame保存在內存。
1 function say667() { 2 // Local variable that ends up within closure 3 var num = 666; 4 var sayAlert = function() { alert(num); } 5 num++; 6 return sayAlert; 7 } 8 var sayNumba = say667(); 9 sayNumba(); //667,而不是666 10 alert(sayNumba.toString());
Example 4
三個函數對某個閉包使用同一個引用,因為它們均在setupSomeGlobals()里聲明的。
1 var gAlertNumber = gIncreaseNumber = gSetNumber = null; 2 function setupSomeGlobals() { 3 // Local variable that ends up within closure 4 var num = 666; 5 // Store some references to functions as global variables 6 gAlertNumber = function() { alert(num); } 7 gIncreaseNumber = function() { num++; } 8 gSetNumber = function(x) { num = x; } 9 } 10 setupSomeGlobals(); 11 //任意、多次 運行下面的函數 12 gAlertNumber(); 13 gIncreaseNumber(); 14 gSetNumber(5); //把num重新設為 5 15 gSetNumber(-8888); //把num重新設為 -8888
重新運行setupSomeGlobals(); 就會重新產生一個新的閉包。在JavaScript中,當你在函數里又聲明一個函數,外部函數每調用一次,內部函數將再被重新產生一次。
Example 5
當心下面例子的循環:閉包中的局部變量可能和你最初想的不一樣。
1 function buildList(list) { 2 var result = []; 3 for (var i = 0; i < list.length; i++) { 4 var item = 'item' + list[i]; 5 result.push( function() {alert(item + ' ' + list[i])} ); 6 } 7 return result; 8 } 9 10 function testList() { 11 var fnlist = buildList([1,2,3]); 12 // using j only to help prevent confusion - could use i 13 for (var j = 0; j < fnlist.length; j++) { 14 fnlist[j](); 15 } 16 } 17 18 testList(); //輸出3次:'item3 undefined'
Example 6
下面的例子表明,閉包包含了 在外部函數退出之前、定義的任何局部變量。注意,變量alice實際上在匿名函數之后聲明的。匿名函數先被聲明:當函數被調用時,它可以訪問alice,因為alice在閉包里。
1 function sayAlice() { 2 var sayAlert = function() { alert(alice); } 3 // Local variable that ends up within closure 4 var alice = 'Hello Alice'; 5 return sayAlert; 6 } 7 sayAlice()(); //Hello Alice 8 alert(alice); //錯誤:alice不是全局變量,它在函數體內var了
Example 7
下面的例子表明,每次調用會產生各自的閉包。
1 function newClosure(someNum, someRef) { 2 // Local variables that end up within closure 3 var num = someNum; 4 var anArray = [1,2,3]; 5 var ref = someRef; 6 return function(x) { 7 num += x; 8 anArray.push(num); 9 alert('num: ' + num + 10 '\nanArray ' + anArray.toString() + 11 '\nref.someVar ' + ref.someVar); 12 } 13 } 14 closure1 = newClosure(40, {someVar : 'closure 1'}); 15 closure1(5); 16 17 closure2 = newClosure(1000, {someVar : 'closure 2'}); 18 closure2(-10);
小結
讀一些說明要比理解上面的例子難得多。我對於閉包的說明以及stack-frame等等在技術上可能不正確 --- 但是它們的確有助於理解