如果回調是在相同的範圍內定義的,則循環定義在(通常是這種情況)中,那麼回調將有權訪問索引變量。撇開細節的NodeJS了一會兒,讓我們考慮這樣的功能:
function doSomething(callback) {
callback();
}
這個函數接受一個回調函數引用,它是所有調用它。不是很令人興奮。 :-)
現在讓我們使用,在一個循環:
var index;
for (index = 0; index < 3; ++index) {
doSomething(function() {
console.log("index = " + index);
});
}
(在計算密集型代碼 —像一個服務器進程 —最好不要從字面上做上面的生產代碼,我們」 LL回來在某一時刻)
現在,當我們運行,我們看到預期的輸出:
index = 0
index = 1
index = 2
我們的回調能夠訪問index
,因爲回調是關閉而不是其定義的範圍內的數據。 (不要擔心術語「關閉」 closures are not complicated。)
我之所以說這可能是最好不要從字面上做上述計算密集型的生產代碼,該代碼創建上每次迭代功能(禁止在編譯器中進行優化,V8非常聰明,但優化創建這些函數並非易事)。所以這裏有一個稍微返工例如:
var index;
for (index = 0; index < 3; ++index) {
doSomething(doSomethingCallback);
}
function doSomethingCallback() {
console.log("index = " + index);
}
這可能看起來有點出人意料,但它仍然以同樣的方式,並且仍然具有相同的輸出,因爲doSomethingCallback
仍然高於index
封閉,所以它仍然看到了值爲index
至於何時被調用。但現在只有一個doSomethingCallback
函數,而不是每個循環中的新鮮函數。
現在,讓我們一個反面的例子,一些不工作:
foo();
function foo() {
var index;
for (index = 0; index < 3; ++index) {
doSomething(myCallback);
}
}
function myCallback() {
console.log("index = " + index); // <== Error
}
失敗,因爲myCallback
不是在同一範圍內(或嵌套的範圍內)定義index
是在定義in,所以index
在myCallback
內未定義。
最後,讓我們考慮在循環中設置事件處理程序,因爲必須注意這一點。在這裏,我們將深入一個的NodeJS位:
var spawn = require('child_process').spawn;
var commands = [
{cmd: 'ls', args: ['-lh', '/etc' ]},
{cmd: 'ls', args: ['-lh', '/usr' ]},
{cmd: 'ls', args: ['-lh', '/home']}
];
var index, command, child;
for (index = 0; index < commands.length; ++index) {
command = commands[index];
child = spawn(command.cmd, command.args);
child.on('exit', function() {
console.log("Process index " + index + " exited"); // <== WRONG
});
}
它似乎像上面應該工作相同的方式,我們前面的循環一樣,但有一個關鍵的區別。在我們之前的循環中,回調被立即調用,所以它看到了正確的index
值,因爲index
還沒有機會繼續前進。但是,在上面,我們將在調用回調之前通過循環。結果?我們看到
Process index 3 exited
Process index 3 exited
Process index 3 exited
這是一個關鍵點。封閉沒有副本它關閉的數據,它有一個現場參考它。因此,在每個進程的exit
回調得到運行時,循環將已完成,因此所有三個調用看到相同的index
值(其值爲循環的端點)。
我們可以通過具有回調使用不同變量不會改變,這樣解決這個問題:
var spawn = require('child_process').spawn;
var commands = [
{cmd: 'ls', args: ['-lh', '/etc' ]},
{cmd: 'ls', args: ['-lh', '/usr' ]},
{cmd: 'ls', args: ['-lh', '/home']}
];
var index, command, child;
for (index = 0; index < commands.length; ++index) {
command = commands[index];
child = spawn(command.cmd, command.args);
child.on('exit', makeExitCallback(index));
}
function makeExitCallback(i) {
return function() {
console.log("Process index " + i + " exited");
};
}
現在我們輸出正確的值(以任何順序流程出口):
Process index 1 exited
Process index 2 exited
Process index 0 exited
是工作的方式是,我們分配給exit
事件回調關閉了在我們對makeExitCallback
呼叫的i
說法。所述第一回調makeExitCallback
創建並返回關閉在該呼叫的i
值makeExitCallback
,它創建的第二個回調關閉超過用於的i
值調用makeExitCallback
(這是比前面呼叫i
值不同),等等。
如果你給the article linked above一個閱讀,一些事情應該更清楚。文章中的術語有點過時(ECMAScript 5使用更新的術語),但概念沒有改變。
我會推薦閱讀[JavaScript Closure Notes](http://jibbering.com/faq/notes/closures/) - nodes.js只是按照規則播放。 – 2010-12-22 07:55:37
@pst:恩,我見過好多了。例如,*「當這些內部函數之一被包含在函數之外時,就形成了一個閉包...」*這是錯誤的。當您創建函數,句號時會創建閉包。該函數是否可以在其包含的範圍之外被訪問是完全不相關的。它也開始談論關閉具有「潛在有害」的影響,雖然事實並非如此。 – 2010-12-22 13:13:06