JavaScript-myArray.forEach与for循环的细微差别


88

我已经看到很多建议使用的问题:

for (var i = 0; i < myArray.length; i++){ /* ... */ }

代替:

for (var i in myArray){ /* ... */ }

对于数组,由于迭代不一致(请参见此处)。


但是,我似乎找不到任何似乎更喜欢面向对象循环的东西:

myArray.forEach(function(item, index){ /* ... */ });

对我来说,这似乎更直观。

对于我当前的项目,IE8兼容性很重要,我正在考虑使用Mozilla的polyfill,但是我不确定100%如何工作。

  • 现代浏览器的for循环标准(上面的第一个示例)与Array.prototype.forEach实现之间是否有区别?
  • 现代浏览器实现与上面链接到的Mozilla的实现之间有什么区别(特别注意IE8)?
  • 性能并不是问题,只是迭代属性的一致性。

6
不可能break退出forEach。但是最大的好处是可以使用该功能创建一个新的作用域。使用polyfill,您应该不会有任何问题(至少我没有遇到任何问题)。
hgoebl 2014年

你可以与旧IE的问题,是不是垫片本身,而是打破数组构造/文字WRT holes这里undefined应该是和其他破碎方法,如slicehasOwnPropertyWRT到arraylike DOM对象。我的测试并es5 shim显示了符合规范的匀场方法(未经测试的MDN匀场片)。
Xotic750

1
而且要打破for循环,这就是要这样做的some原因。
Xotic750

1
“但是,我似乎找不到任何似乎更喜欢面向对象循环的东西:”我宁愿称其为功能方式与命令方式。
Memke '16

Array.find()找到第一个匹配项后,您可以用来突破循环。
Mottie '16

Answers:


121

for循环和forEach方法之间最实质的区别在于,对于前者,您可能会break跳出循环。您可以continue通过简单地从传递给的函数返回来进行模拟forEach,但是无法完全停止循环。

除此之外,两者有效地实现了相同的功能。由于变量提升,另一个小的差异涉及for循环中索​​引的范围(以及所有包含变量的变量)。

// 'i' is scoped to the containing function
for (var i = 0; i < arr.length; i++) { ... }

// 'i' is scoped to the internal function
arr.forEach(function (el, i) { ... });

但是,我发现它forEach更具表现力-它表示您遍历数组的每个元素的意图,并且为您提供了对该元素的引用,而不仅仅是索引。总体而言,它主要取决于个人喜好,但如果可以使用forEach,我建议您使用它。


两个版本之间还有一些实质性差异,特别是在性能方面。实际上,forEach本jsperf测试所示,简单的for循环比该方法的性能要好得多。

是否需要这种性能取决于您自己决定,在大多数情况下,我倾向于表现力胜于速度。此速度差异可能是由于在稀疏数组上进行操作时基本循环与方法之间的语义差异较小,如本答案所述

如果您不需要行为forEach和/或需要尽早突破循环,则可以使用Lo-Dash _.each作为替代,它也可以跨浏览器工作。如果您使用的是jQuery,它还提供了一个类似的$.each,只需注意每个变体中传递给回调函数的参数的差异。

(对于forEachpolyfill,如果您选择采用这种方法,它应该可以在较旧的浏览器中正常工作。)


1
值得注意的是,库版本的each行为方式与的ECMA5规范不同forEach,它们倾向于将所有数组视为密集数组(如果您知道,则应避免IE错误)。否则可以是“陷阱”。作为参考github.com/es-shims/es5-shim/issues/190
Xotic750

7
另外,还有一些原型方法可以让您中断工作,例如,Array.prototype.some这些方法将一直循环直到您返回真实值或直到它已完全遍历数组为止。Array.prototype.every与相似,Array.prototype.some但是如果返回假值则停止。
axelduch

如果您想在不同的浏览器和此类垫片上查看测试结果,可以在此处查看ci.testling.com/Xotic750/util-x
Xotic750 2014年

另外,使用Array.prototype.forEach会使代码受扩展的影响。例如,如果您正在编写要嵌入到页面中的代码,则Array.prototype.forEach有可能被其他内容覆盖。
Marble Daemon

2
@MarbleDaemon这样做的机会是如此之小,以至于实际上是不可能的,因此可以忽略不计。Array.prototype.forEach用某些不兼容的版本进行覆盖可能会破坏许多库,因此无论如何避免这样做都是无济于事的。
亚历克西斯·金

12

您可以使用自定义的foreach函数,其性能将比Array.forEach好得多

您应该将此添加一次到您的代码。这将向阵列添加新功能。

function foreach(fn) {
    var arr = this;
    var len = arr.length;
    for(var i=0; i<len; ++i) {
        fn(arr[i], i);
    }
}

Object.defineProperty(Array.prototype, 'customForEach', {
    enumerable: false,
    value: foreach
});

然后您可以在Array.forEach之类的任何地方使用它

[1,2,3].customForEach(function(val, i){

});

唯一的区别是它快了3倍。https://jsperf.com/native-arr-foreach-vs-custom-foreach

更新:在新的Chrome版本中,.forEach()的性能得到了改善。但是,该解决方案可以在其他浏览器中提供额外的性能。

JSPerf


4

许多开发人员(例如Kyle Simpson)建议使用它.forEach来表示该数组将产生副作用并.map用于纯函数。for对于已知数量的循环或任何其他不合适的情况,循环很适合作为通用解决方案,因为它对大多数编程语言都提供广泛支持,因此更易于通信。

例如

/* For Loop known number of iterations */
const numberOfSeasons = 4;
for (let i = 0; i < numberOfSeasons; i++) {
  //Do Something
}

/* Pure transformation */
const arrayToBeUppercased = ['www', 'html', 'js', 'us'];
const acronyms = arrayToBeUppercased.map((el) => el.toUpperCase));

/* Impure, side-effects with .forEach */
const acronymsHolder = [];
['www', 'html', 'js', 'us'].forEach((el) => acronymsHolder.push(el.toUpperCase()));

从约定的角度来看,这似乎是最好的,但是社区尚未真正确定要在较新的迭代协议for in循环上使用约定。通常,我认为遵循JS社区似乎愿意采用的FP概念是一个好主意。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.