1 回答

TA貢獻(xiàn)1802條經(jīng)驗(yàn) 獲得超6個(gè)贊
Node.js 的異步機(jī)制由事件和回調(diào)函數(shù)實(shí)現(xiàn),一開始接觸可能會(huì)感覺違反常規(guī),但習(xí)慣 以后就會(huì)發(fā)現(xiàn)還是很簡(jiǎn)單的。然而這之中其實(shí)暗藏了不少陷阱,一個(gè)很容易遇到的問題就是 循環(huán)中的回調(diào)函數(shù),初學(xué)者經(jīng)常容易陷入這個(gè)圈套。讓我們從一個(gè)例子開始說明這個(gè)問題。
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
for (var i = 0; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', function (err, contents) {
console.log(files[i] + ': ' + contents);
});
}
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
for (var i = 0; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', function (err, contents) {
console.log(files);
console.log(i);
console.log(files[i]);
});
}
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
for (var i = 0; i < files.length; i++) {
(function (i) {
fs.readFile(files[i], 'utf-8', function (err, contents) {
console.log(files[i] + ': ' + contents);
});
})(i);
}
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
files.forEach(function (filename) {
fs.readFile(filename, 'utf-8', function (err, contents) {
console.log(filename + ': ' + contents);
});
});
這段代碼的功能很直觀,就是依次讀取文件 a.txt、b.txt 、c.txt ,并輸出文件名和內(nèi)容。假設(shè)這三個(gè)文件的內(nèi)容分別是 AAA 、BBB 和 CCC,那么我們期望的輸出結(jié)果就是:
a.txt: AAA
b.txt: BBB
c.txt: CCC
可是我們運(yùn)行這段代碼的結(jié)果是怎樣的呢?竟然是這樣的結(jié)果:
undefined: AAA
undefined: BBB
undefined: CCC
這個(gè)結(jié)果說明文件內(nèi)容正確輸出了,而文件名卻不對(duì),也就意味著,contents 的結(jié)果是正確的,但 files[i] 的值是 undefined。這怎么可能呢,文件名不正確卻能讀取文件內(nèi)容?既然難以直觀地理解,我們就把 files[i] 分解并打印出來看看,在讀取文件的回調(diào)函數(shù)中分別輸出 files、i 和 files[i] 。
運(yùn)行修改后的代碼,結(jié)果如下:
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
看到這里是不是有點(diǎn)啟發(fā)了呢?三次輸出的 i 的值都是 3 ,超出了 files 數(shù)組的下標(biāo)范圍,因此 files[i] 的值就是 undefined 了。這種情況通常會(huì)在 for 循環(huán)結(jié)束時(shí)發(fā)生,例如 for (var i = 0; i < files.length; i++),退出循環(huán)時(shí) i 的值就files.length 的值。既然 i 的值是 3 ,那么說明了事實(shí)上 fs.readFile 的回調(diào)函數(shù)中訪問到的 i 值都是循環(huán)退出以后的,因此不能分辨。而 files[i] 作為 fs.readFile 的第一個(gè)參數(shù)在循環(huán)中就傳遞了,所以文件可以被定位到,而且可以顯示出文件的內(nèi)容。
現(xiàn)在問題就明朗了:原因是3 次讀取文件的回調(diào)函數(shù)事實(shí)上是同一個(gè)實(shí)例,其中引用到的 i 值是上面循環(huán)執(zhí)行結(jié)束后的值,因此不能分辨。如何解決這個(gè)問題呢?我們可以利用
JavaScript 函數(shù)式編程的特性,手動(dòng)建立一個(gè)閉包:
//forloopclosure.js
上面代碼在 for 循環(huán)體中建立了一個(gè)匿名函數(shù),將循環(huán)迭代變量 i 作為函數(shù)的參數(shù)傳遞并調(diào)用。由于運(yùn)行時(shí)閉包的存在,該匿名函數(shù)中定義的變量(包括參數(shù)表)在它內(nèi)部的函數(shù)(fs.readFile 的回調(diào)函數(shù))執(zhí)行完畢之前都不會(huì)釋放,因此我們?cè)谄渲性L問到的 i 就分別是不同的閉包實(shí)例,這個(gè)實(shí)例是在循環(huán)體執(zhí)行的過程中創(chuàng)建的,保留了不同的值。
補(bǔ)充:閉包的寫法,無(wú)法保證按數(shù)組存放文件順序讀取文件內(nèi)容,相當(dāng)多個(gè)文件讀取操作并行進(jìn)行,根據(jù)文件大小決定讀取的快慢;而forEach是可以的保證順序讀取;
事實(shí)上以上這種寫法并不常見,因?yàn)樗档土顺绦虻目勺x性,故不推薦使用。大多數(shù)情況下我們可以用數(shù)組的 forEach 方法解決這個(gè)問題:
//callbackforeach.js
- 1 回答
- 0 關(guān)注
- 1544 瀏覽
添加回答
舉報(bào)