《Nodejs开发指南》for与forEach之间的差别

《Nodejs开发指南》for与forEach之间的差别

作者提到循环的陷阱

for( var i = 0;i<files.length;i++){
    fs.readFile(files[i],'utf-8',function (err,contents) {
        console.log(files[i] + ':' + contents);
    })
}

结果是

undefined:AAA
undefined:BBB
undefined:CCC

这个不难理解,作者建议改成这样,但是没有解释

files.forEach(function (filename) {
    fs.readFile(filename,'utf-8',function (err,contents) {
        console.log(filename+':'+contents);
    })
})

我不懂,为什么把for改成forEach就解决问题了,for与forEach本质区别在哪里


13 回复

《Nodejs开发指南》for与forEach之间的差别

问题背景

在Node.js中,你可能会遇到使用for循环和Array.prototype.forEach()处理异步操作时出现的不同结果。本文将详细解释这两种方法的区别,并提供相应的示例代码。

示例代码

首先来看一个使用for循环的例子:

for (var i = 0; i < files.length; i++) {
    fs.readFile(files[i], 'utf-8', function (err, contents) {
        console.log(files[i] + ':' + contents);
    });
}

这段代码的结果会输出一系列undefined

undefined:AAA
undefined:BBB
undefined:CCC

这是因为在回调函数执行时,i的值已经变成了files.length(即超出数组索引的值),所以files[i]undefined

使用 forEach 解决问题

接下来,我们看看使用forEach的情况:

files.forEach(function (filename) {
    fs.readFile(filename, 'utf-8', function (err, contents) {
        console.log(filename + ':' + contents);
    });
});

这段代码可以正确地输出文件内容:

AAA:...
BBB:...
CCC:...

原因分析

  1. 作用域问题

    • for循环中,变量i是全局变量,在循环结束时它的值为files.length。因此,当异步回调函数被执行时,i的值已经是files.length,导致files[i]undefined
  2. 闭包问题

    • forEach内部创建了一个新的作用域,每次迭代都会创建一个新的闭包,使得每次回调函数都能捕获到当前迭代的filename值。因此,即使异步操作在循环结束后才完成,每个回调函数仍然能访问到正确的filename
  3. 性能考虑

    • forEach在内部实现上更简洁,但如果你需要提前终止循环,可能需要使用其他方法如someevery

总结

  • for循环:在处理异步操作时容易出错,因为循环变量的作用域和闭包问题可能导致意外的结果。
  • forEach:通过创建新的闭包来确保每次迭代都有独立的作用域,从而避免了上述问题。

通过上述分析,我们可以看到,使用forEach能够更好地处理异步操作中的作用域问题,从而避免了输出undefined的情况。


这是经典的js闭包问题,题主可自行查阅

forEach算是FP的半成品

这个其实跟 for 和 forEach 没什么关系,是写 javascript 闭包的一个经常犯的错误,可参考: Creating closures in loops: A common mistake

闭包会把当前的环境保存下来,原来的代码里面那个 for 创建了若干个闭包,但是每个闭包共享上下文环境 i。因为 for (很大可能)会先跑完,所以运行回调函数的时候 i 已经变成了 files.length,这时候 files[i] 因为超过数组边界,所以就 undefined 了。

写 for 其实也没问题,用 function factory 就可以。

function gencb(filename) {
    return function (err, contents) {
        if (err) //...
        console.log(filename + ':' + contents);
    };
};

for( var i = 0;i<files.length;i++){ fs.readFile(files[i],‘utf-8’, gencb(files[i])) }

这个代码因为每次闭包都不一样(包括filename),所以就正常了

for循环的时候i每次都是files.length所以找不到。 也可以这样改: 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); }

感谢,对闭包的理解又深了一步 也就是说forEach不存在上下文环境这个问题?

使用js必然会遇见的闭包问题

for( var i = 0;i<files.length;i++){

fs.readFile(files[i],'utf-8',function (err,contents) {  
		//这一部分是异步执行的,其实差不多等到for循环完成才会执行,所以这时候的i==files.length ,懂了吗?
		console.log(files[i] + ':' + contents);
})

}

顶一个,很好的知识点。

i 每次循环 复制一份就好啦

闭包啊

包装一层函数(闭包)会保持当前参数,so~

forforEach 都可以用来遍历数组,但它们在实现方式上有一些关键的区别,尤其是在处理异步操作时。

for 循环

当你使用 for 循环时,变量 i 是定义在循环外部的。这意味着在整个循环中,i 的值都在变化,直到循环结束。因此,在异步操作(如 fs.readFile)完成时,循环可能已经执行完毕,此时 i 的值不再是数组中的某个有效索引。这就是为什么你会看到 undefined 的原因。

// 示例代码
const files = ['AAA', 'BBB', 'CCC'];
for (var i = 0; i < files.length; i++) {
    fs.readFile(files[i], 'utf-8', function (err, contents) {
        console.log(files[i] + ':' + contents); // files[i] 在这里已经变成了最后一个文件名之后的值
    });
}

forEach 方法

forEach 方法内部创建了一个新的作用域,每次回调函数都会在一个新的作用域中执行。这意味着在每个回调函数中,filename 变量始终是指当前正在处理的文件名,不会受到外部循环的影响。

// 示例代码
const files = ['AAA', 'BBB', 'CCC'];
files.forEach(function (filename) {
    fs.readFile(filename, 'utf-8', function (err, contents) {
        console.log(filename + ':' + contents);
    });
});

总结

forEach 解决了 for 循环中的变量作用域问题,确保每次回调函数中 filename 的值都是正确的。而 for 循环由于变量 i 的共享,导致异步操作完成时,其值已经变化。

回到顶部