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

Posted

技术标签:

【中文标题】JavaScript - myArray.forEach 与 for 循环的细微差别【英文标题】:JavaScript - Nuances of myArray.forEach vs for loop 【发布时间】:2016-11-03 12:12:03 【问题描述】:

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

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

代替:

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

对于数组,由于不一致的迭代 (see here)。


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

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

这对我来说似乎更直观。

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

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

【问题讨论】:

不可能从forEach 中取出break。但是一个很大的优势是使用该功能创建一个新的范围。使用 polyfill 你应该没有任何问题(至少我没有遇到任何问题)。 旧 IE 可能遇到的问题不是 shim 本身,而是损坏的数组构造函数/文字 wrt holes 其中应该是 undefined 和其他损坏的方法,例如 slicehasOwnProperty wrt 到类似数组的 DOM 对象。我的测试和es5 shim 显示了这种符合规范的垫片方法(未测试 MDN 的垫片)。 要打破for 循环,这就是some 的用途。 “但是,我似乎找不到任何似乎更喜欢面向对象循环的东西:”我宁愿将其称为函数式方式而不是命令式方式。 您可以在找到第一个匹配项后使用Array.find() 跳出循环。 【参考方案1】:

for 循环和forEach 方法之间最本质的区别在于,使用前者,您可以将break 排除在循环之外。您可以通过简单地从传递给forEach 的函数返回来模拟continue,但没有办法完全停止循环。

除此之外,两者还有效地完成了相同的功能。由于变量提升,另一个细微差别涉及 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,我会推荐使用它。


两个版本之间还有一些实质性的差异,特别是在性能方面。事实上,简单的 for 循环比forEach 方法的性能要好得多,正如this jsperf test 所证明的那样。

您是否需要这种性能取决于您自己决定,在大多数情况下,我更倾向于表现力而不是速度。如this answer 中所述,这种速度差异可能是由于基本循环和在稀疏数组上操作时方法之间存在细微的语义差异。

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

(至于forEach polyfill,它应该可以在较旧的浏览器中正常工作,如果您选择走那条路线。)

【讨论】:

值得注意的是,库版本 each 的行为方式与 forEach 的 ECMA5 规范不同,它们倾向于将所有数组视为密集的(为了避免 IE 错误,提供你知道的)。否则可能是“陷阱”。作为参考github.com/es-shims/es5-shim/issues/190 还有一些原型方法可以让你中断,例如Array.prototype.some,它将循环直到你返回一个真实值或直到它完全循环通过数组。 Array.prototype.everyArray.prototype.some 类似,但如果返回虚假值,则停止。 如果你想在不同的浏览器和类似的垫片上看到一些测试结果,你可以看看这里,ci.testling.com/Xotic750/util-x 另外,使用 Array.prototype.forEach 会使你的代码受扩展的支配。例如,如果您正在编写要嵌入到页面中的代码,则 Array.prototype.forEach 可能会被其他内容覆盖。 @MarbleDaemon 这种可能性非常小,实际上是不可能的,因此可以忽略不计。用一些不兼容的版本覆盖Array.prototype.forEach 可能会破坏如此多的库,因此无论如何避免它也无济于事。【参考方案2】:

您可以使用自定义的 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() 的性能得到了改进。但是,该解决方案可以在其他浏览器中提供额外的性能。

【讨论】:

【参考方案3】:

许多开发人员(例如 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 概念是个好主意。

【讨论】:

以上是关于JavaScript - myArray.forEach 与 for 循环的细微差别的主要内容,如果未能解决你的问题,请参考以下文章

javascript JavaScript isset()等效: - JavaScript

JavaScript 使用JavaScript更改CSS(JavaScript)

JavaScript之基础-1 JavaScript(概述基础语法)

前端基础-JavaScript的基本概述和语法

JavaScript

JavaScript