题 For-each在JavaScript中的数组?


如何使用JavaScript循环遍历数组中的所有条目?

我以为它是这样的:

forEach(instance in theArray)

哪里 theArray 是我的阵列,但这似乎是不正确的。


3791
2018-02-17 13:51


起源


我确实找了它,但我找了 forEach  而不只是 for。如上所述,在c#中它有点不同,这让我困惑:) - Dante1986
ECMAScript  6可能包含“for ... of”构造。看到 为...的 (MDN)了解更多信息。您可以使用最新的Firefox版本进行试用。 - Slaven Rezic
对于JavaScript中的Arrays,Array.ForEach比for()中的约慢95%。在线查看此性能测试: jsperf.com/fast-array-foreach 通过 coderwall.com/p/kvzbpa - molokoloco
对于许多情况来说,95%的慢速并不重要 blog.niftysnippets.org/2012/02/foreach-and-runtime-cost.html - David Sykes
相比之下,在 蟒蛇 它是 更高效 使用函数而不是使用传统的for循环。 (考虑一下 i < len 和 i++ 可以由引擎完成,而不是由解释器完成。) - joeytwiddle


答案:


TL; DR

  • 不要用 for-in 除非你将它与保护措施一起使用,或者至少知道它为什么会咬你。
  • 你最好的赌注通常是

    • 一个 for-of 循环(仅限ES2015 +),
    • Array#forEach (spec | MDN)(或其亲属 some 等(仅限ES5 +),
    • 一个简单的老式 for 循环,
    • 要么 for-in 有保障措施。

但是有 地段 更多探索,继续阅读......


JavaScript具有强大的语义,可以循环遍历数组和类似数组的对象。我把答案分成两部分:正版数组的选项,以及只是数组的选项 - 喜欢, 如那个 arguments 对象,其他可迭代对象(ES2015 +),DOM集合等。

我很快就会注意到您可以使用ES2015选项 现在,甚至在ES5引擎上,通过 transpiling ES2015到ES5。搜索“ES2015 transpiling”/“ES6 transpiling”了解更多...

好的,让我们来看看我们的选择:

对于实际数组

你有三个选择 ECMAScript的5 (“ES5”),目前最广泛支持的版本,还有两个版本 2015年的ECMAScript (“ES2015”,“ES6”):

  1. 使用 forEach 和相关(ES5 +)
  2. 用一个简单的 for 循环
  3. 使用 for-in  正确地
  4. 使用 for-of (隐式使用迭代器)(ES2015 +)
  5. 明确使用迭代器(ES2015 +)

细节:

1.使用 forEach 和相关的

在任何模糊的现代环境(所以,不是IE8),你可以访问 Array 由ES5添加的功能(直接或使用polyfills),您可以使用 forEach (spec | MDN):

var a = ["a", "b", "c"];
a.forEach(function(entry) {
    console.log(entry);
});

forEach 接受回调函数,并且可选地接受要用作的值 this 调用该回调时(上面没有使用)。为数组中的每个条目调用回调,按顺序跳过稀疏数组中不存在的条目。虽然我上面只使用了一个参数,但回调是用三个来调用的:每个条目的值,该条目的索引,以及对你要迭代的数组的引用(如果你的函数还没有方便的话) )。

除非您支持IE8等过时的浏览器(截至2016年9月,NetApps的市场份额仅超过4%),您可以愉快地使用 forEach 在没有垫片的通用网页中。如果您确实需要支持过时的浏览器,则填充/填充 forEach 很容易完成(为几个选项搜索“es5 shim”)。

forEach 有一个好处是你不必在包含作用域中声明索引和值变量,因为它们作为参数提供给迭代函数,因此很好地限定了该迭代。

如果您担心为每个数组条目调用函数的运行时成本,请不要这样做; 细节

另外, forEach 是“循环通过它们”的功能,但ES5定义了其他一些有用的“按照你的方式通过数组并做事”的功能,包括:

  • every (第一次回调返回时停止循环 false 或者是虚假的东西)
  • some (第一次回调返回时停止循环 true 或者某事真相)
  • filter (创建一个新数组,包括过滤函数返回的元素 true 并省略它返回的那些 false
  • map (根据回调返回的值创建一个新数组)
  • reduce (通过重复调用回调来构建一个值,传入以前的值;查看详细信息的规范;对于汇总数组的内容和许多其他内容很有用)
  • reduceRight (喜欢 reduce,但按降序而不是按升序排列)

2.用一个简单的 for 循环

有时旧的方式是最好的:

var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
    console.log(a[index]);
}

如果数组的长度在循环期间不会改变,并且它在性能敏感的代码中(不太可能),那么在前面抓取长度的稍微复杂的版本可能是  更快一点:

var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
    console.log(a[index]);
}

和/或向后计数:

var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
    console.log(a[index]);
}

但是使用现代JavaScript引擎,你很少需要榨取最后一点果汁。

在ES2015及更高版本中,您可以将索引和值变量置于本地 for 循环:

let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
}
//console.log(index); // Would cause "ReferenceError: index is not defined"
//console.log(value); // Would cause "ReferenceError: value is not defined"

当你这样做时,不仅仅是 value 但也 index 为每个循环迭代重新创建,这意味着在循环体中创建的闭包保持对该循环的引用 index (和 value)为特定迭代创建:

let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
    divs[index].addEventListener('click', e => {
        alert("Index is: " + index);
    });
}

如果您有五个div,如果单击第一个,则“索引为:0”,如果单击最后一个,则“索引为:4”。这样做  如果你使用,工作 var 代替 let

3.使用 for-in  正确地

你会让别人告诉你使用 for-in但是 那不是什么 for-in 是为了for-in 循环通过 对象的可枚举属性,而不是数组的索引。 订单无法保证,甚至在ES2015(ES6)中也没有。 ES2015确实定义了对象属性的顺序(通过 [[OwnPropertyKeys]][[Enumerate]],以及使用它们的东西 Object.getOwnPropertyKeys), 但它 才不是 定义那个 for-in 将按照该顺序。 (详情请见 这个答案。)

不过,它 能够 有用,特别是对  阵列,如果您使用适当的保障措施:

// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
    if (a.hasOwnProperty(key)  &&        // These are explained
        /^0$|^[1-9]\d*$/.test(key) &&    // and then hidden
        key <= 4294967294                // away below
        ) {
        console.log(a[key]);
    }
}

请注意两个检查:

  1. 对象有它的 拥有 该名称的属性(不是它从原型继承的那个),和

  2. 密钥是普通字符串形式的基数为10的数字字符串,其值为<= 2 ^ 32 - 2(即4,294,967,294)。这个数字来自哪里?它是数组索引定义的一部分 在规范中。其他数字(非整数,负数,大于2 ^ 32 - 2的数字)不是数组索引。它的原因是2 ^ 32 - 2 是使得最大指数值低于2 ^ 32 - 1,这是数组的最大值 length 可以有。 (例如,数组的长度适合32位无符号整数。) (道具指向RobG在评论中指出 在我的博客文章中 我之前的测试不太正确。)

对于大多数阵列来说,这只是每次循环迭代的一小部分额外开销,但如果你有一个  数组,它可以是一种更有效的循环方式,因为它只循环实际存在的条目。例如,对于上面的数组,我们总共循环三次(对于键 "0""10",和 "10000" - 记住,他们是字符串),而不是10,001次。

现在,您不希望每次都写这个,所以您可以将它放在您的工具箱中:

function arrayHasOwnIndex(array, prop) {
    return array.hasOwnProperty(prop) && /^0$|^[1-9]\d*$/.test(prop) && prop <= 4294967294; // 2^32 - 2
}

然后我们就像这样使用它:

for (key in a) {
    if (arrayHasOwnIndex(a, key)) {
        console.log(a[key]);
    }
}

或者如果你只对“大多数情况下足够好”的测试感兴趣,你可以使用它,但是当它接近时,它并不完全正确:

for (key in a) {
    // "Good enough" for most cases
    if (String(parseInt(key, 10)) === key && a.hasOwnProperty(key)) {
        console.log(a[key]);
    }
}

4.使用 for-of (隐式使用迭代器)(ES2015 +)

ES2015补充道 迭代器 到JavaScript。使用迭代器的最简单方法是使用新方法 for-of 声明。它看起来像这样:

var val;
var a = ["a", "b", "c"];
for (val of a) {
    console.log(val);
}

输出:

一个
b
C

在封面下,这得到了 迭代器 从数组中循环遍历它,从中获取值。这没有使用的问题 for-in has,因为它使用由对象(数组)定义的迭代器,并且数组定义它们的迭代器遍历它们  (不是他们的财产)。不像 for-in 在ES5中,访问条目的顺序是其索引的数字顺序。

5.明确使用迭代器(ES2015 +)

有时,您可能希望使用迭代器 明确地。你也可以做到这一点,虽然它比笨笨更多 for-of。它看起来像这样:

var a = ["a", "b", "c"];
var it = a.values();
var entry;
while (!(entry = it.next()).done) {
    console.log(entry.value);
}

迭代器是与规范中的Iterator定义匹配的对象。它的 next 方法返回一个新的 结果对象 每次你打电话。结果对象有一个属性, done告诉我们它是否已经完成,以及财产 value 使用该迭代的值。 (done 是可选的,如果是的话 falsevalue 是可选的,如果是的话 undefined。)

的意思 value 取决于迭代器;数组支持(至少)三个返回迭代器的函数:

  • values():这是我上面使用的那个。它返回一个迭代器,每个迭代器 value 是该迭代的数组条目("a""b",和 "c" 在前面的例子中)。
  • keys():返回每个迭代器 value 是那次迭代的关键(所以我们的 a 以上,那就是 "0", 然后 "1", 然后 "2")。
  • entries():返回每个迭代器 value 是表单中的数组 [key, value] 对于那次迭代。

对于类似数组的对象

除了真正的数组,还有 阵列状 有一个的对象 length 具有数字名称的属性和属性: NodeList 实例, arguments 对象等。我们如何循环其内容?

使用上面的任何数组选项

上面的阵列方法中的至少一些,可能是大多数甚至全部,经常同样适用于类似数组的对象:

  1. 使用 forEach 和相关(ES5 +)

    各种功能 Array.prototype 是“故意通用的”,通常可以用于类似数组的对象 Function#call 要么 Function#apply。 (见 警告主机提供的对象 在这个答案的最后,但这是一个罕见的问题。)

    假设你想使用 forEach 在...上 NodechildNodes 属性。你这样做:

    Array.prototype.forEach.call(node.childNodes, function(child) {
        // Do something with `child`
    });
    

    如果你要做很多事情,你可能想把一个函数引用的副本放到一个变量中以便重用,例如:

    // (This is all presumably in some scoping function)
    var forEach = Array.prototype.forEach;
    
    // Then later...
    forEach.call(node.childNodes, function(child) {
        // Do something with `child`
    });
    
  2. 用一个简单的 for 循环

    显然,简单 for loop适用于类似数组的对象。

  3. 使用 for-in  正确地

    for-in 使用与数组相同的安全措施也应该使用类似数组的对象;上面#1上主机提供的对象的警告可能适用。

  4. 使用 for-of (隐式使用迭代器)(ES2015 +)

    for-of 将使用对象提供的迭代器(如果有的话);我们将不得不看到它如何与各种类似数组的对象一起使用,特别是主机提供的对象。例如,规范 NodeList 从 querySelectorAll 已更新以支持迭代。该规范 HTMLCollection 从 getElementsByTagName 不是。

  5. 明确使用迭代器(ES2015 +)

    看到#4,我们必须看看迭代器是如何发挥作用的。

创建一个真正的数组

其他时候,您可能希望将类似数组的对象转换为真正的数组。这样做非常简单:

  1. 使用 slice 数组方法

    我们可以使用 slice 数组的方法,与上面提到的其他方法一样,是“故意通用的”,因此可以与类似数组的对象一起使用,如下所示:

    var trueArray = Array.prototype.slice.call(arrayLikeObject);
    

    所以,例如,如果我们想转换一个 NodeList 进入一个真正的数组,我们可以做到这一点:

    var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
    

    警告主机提供的对象 下面。特别要注意的是,这将在IE8及更早版本中失败,因为它不允许您使用主机提供的对象 this 像那样。

  2. 使用 传播语法(...

    也可以使用ES2015 传播语法 使用支持此功能的JavaScript引擎:

    var trueArray = [...iterableObject];
    

    所以,例如,如果我们想转换一个 NodeList 进入一个真正的数组,使用扩展语法,这变得非常简洁:

    var divs = [...document.querySelectorAll("div")];
    
  3. 使用 Array.from  (SPEC) | (MDN)

    Array.from (ES2015 +,但容易polyfilled)从类似数组的对象创建一个数组,可选择首先通过映射函数传递条目。所以:

    var divs = Array.from(document.querySelectorAll("div"));
    

    或者,如果您想获得具有给定类的元素的标记名称数组,则可以使用映射函数:

    // Arrow function (ES2015):
    var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
    
    // Standard function (since `Array.from` can be shimmed):
    var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
        return element.tagName;
    });
    

警告主机提供的对象

如果你使用 Array.prototype 功能 主机提供的 类似于数组的对象(DOM列表和浏览器提供的其他内容而不是JavaScript引擎),您需要确保在目标环境中进行测试,以确保主机提供的对象行为正常。 大多数人表现得很好 (现在),但测试很重要。原因是大部分的 Array.prototype 您可能想要使用的方法依赖于主机提供的对象,为抽象提供诚实的答案 [[HasProperty]] 操作。在撰写本文时,浏览器在这方面做得很好,但5.1规范确实允许主机提供的对象可能不诚实。在里面 §8.6.2,在该部分开头附近的大表下方的几个段落),其中说:

除非另有说明,否则Host对象可以以任何方式实现这些内部方法;例如,一种可能性是 [[Get]] 和 [[Put]] 对于特定的宿主对象确实获取和存储属性值但是 [[HasProperty]] 永远生成

(我无法在ES2015规范中找到相同的措辞,但它仍然是这样的。)同样,在撰写本文时,现代浏览器中常见的主机提供的类似数组的对象[NodeList 实例,例如]  处理 [[HasProperty]] 正确,但测试很重要。)


5980
2018-02-17 13:53



我还想补充一点 .forEach 无法有效打破。你必须抛出异常来执行休息。 - Pijusn
@Pius:如果你想打破循环,你可以使用 some。 (我本来希望允许打破 forEach 同样,但他们,嗯,没有问我。 ;-)) - T.J. Crowder
@ T.J.Crowder是的,即使它看起来更像是一种解决方案,因为它不是它的主要目的。 - Pijusn
@ user889030:你需要一个 , 后 k=0不是 ;。记住,编程是很多东西,其中之一是密切关注细节... :-) - T.J. Crowder
@JimB:上面已经介绍过了(和 length 不是一种方法)。 :-) - T.J. Crowder


编辑:这个答案毫无希望地过时了。如需更现代的方法,请查看 数组上可用的方法。感兴趣的方法可能是:

  • 的forEach
  • 地图
  • 过滤
  • 压缩
  • 减少
  • 一切
  • 一些

迭代数组的标准方法 JavaScript的 是香草 for-循环:

var length = arr.length,
    element = null;
for (var i = 0; i < length; i++) {
  element = arr[i];
  // Do something with element i.
}

但请注意,只有拥有密集数组且每个索引都被一个元素占用时,此方法才有用。如果数组是稀疏的,那么你可能会遇到这种方法的性能问题,因为你将迭代很多不具备这种方法的索引  存在于数组中。在这种情况下,一个 for .. in-loop可能是个更好的主意。 然而,您必须使用适当的安全措施来确保只对数组的所需属性(即数组元素)起作用,因为 for..in-loop也将在旧版浏览器中枚举,或者如果将其他属性定义为 enumerable

ECMAScript 5 在数组原型上会有一个forEach方法,但在旧版浏览器中不支持它。因此,为了能够始终如一地使用它,您必须具有支持它的环境(例如, Node.js的 对于服务器端JavaScript),或使用“Polyfill”。然而,Polyfill对于这个功能是微不足道的,因为它使代码更容易阅读,所以它是一个很好的polyfill。


451
2018-02-17 13:55



为什么是 for(instance in objArray)  不正确的用法?它对我来说看起来更简单,但我听说你说它不是正确的使用方式? - Dante1986
您可以使用内联长度缓存:for(var i = 0,l = arr.length; i <l; i ++) - Robert
第一行末尾的逗号是故意的,还是拼写错误(可以是分号)? - 9KSoft
@ wardha-Web这是故意的。它使我们能够用一个声明多个变量 var-关键词。如果我们使用了分号,那么 element 本来可以在全球范围内宣布(或者说,JSHint在它到达生产之前会尖叫我们)。 - PatrikAkerstrand


如果你正在使用 jQuery的 库,你可以使用 jQuery.each

$.each(yourArray, function(index, value) {
  // do your stuff here
});

编辑: 

根据问题,用户想要javascript中的代码而不是jquery,所以编辑是

var length = yourArray.length;   
for (var i = 0; i < length; i++) {
  // Do something with yourArray[i].
}

205
2018-02-17 14:01



我可能会最常使用这个答案。这不是问题的最佳答案,但在实践中,对于我们这些使用jQuery的人来说,这是最简单和最适用的。我认为我们都应该学习香草的方式。扩大理解永远不会伤害。 - mopsyd
只是为了它:jQuery每个都比原生解决方案慢得多。 jQuery建议在可能的情况下使用本机JavaScript而不是jQuery。 jsperf.com/browser-diet-jquery-each-vs-for-loop - Kevin Boss
当你可以使用vanilla js时,不要使用jQuery - Noe
坚持使用标准JS,除非没有本地语言解决方案,否则请保留第三方库 - Steve K
有点让我想起这个: i.stack.imgur.com/ssRUr.gif - Ajedi32


向后循环

我觉得 相反 for循环值得一提:

for (var i = array.length; i--; ) {
     // process array[i]
}

优点:

  • 您不需要声明临时 len 变量,或比较 array.length 在每次迭代中,其中任何一个都可能是一个小时优化。
  • 删除兄弟姐妹 通常以相反的顺序来自DOM 更高效。 (浏览器需要减少内部数组中元素的移动。)
  • 如果你 修改数组 循环时,在索引处或之后 一世 (例如,您删除或插入项目 array[i]),然后前进循环将跳过向左移动到位的项目 一世,或重新处理 一世那个向右移动的物品。在传统的for循环中,你 可以 更新 一世 指向需要处理的下一个项目 - 1,但简单地反转迭代方向通常是一个 简单 和 更优雅的解决方案
  • 同样,在修改或删除时 嵌套 DOM元素,反向处理即可 规避错误。例如,在处理子节点之前,请考虑修改父节点的innerHTML。到达子节点时,它将与DOM分离,在写入父内部HTML时,已被新创建的子节点替换。
  • 它是  打字,和 ,比其他一些可用的选项。虽然它输了 forEach() 和ES6的 for ... of

缺点:

  • 它以相反的顺序处理项目。如果您要从结果中构建新数组,或者在屏幕上打印内容,那么这很自然 输出将被反转 关于原始订单。
  • 为了保留他们的顺序,反复插入兄弟姐妹作为第一个孩子的DOM 效率低下。 (浏览器将不得不改变方向。)要按顺序有效地创建DOM节点,只需循环前进并正常追加(并使用“文档片段”)。
  • 反向循环是 扑朔迷离 对初级开发人员。 (你可以认为这是一个优势,取决于你的前景。)

我应该经常使用它吗?

一些开发人员使用reverse for循环 默认,除非有充分的理由继续前进。

虽然性能提升通常微不足道,但它有点尖叫:

“只需对列表中的每个项目执行此操作,我不关心订单!”

然而在实践中  实际上是意图的可靠指示,因为它与你的那些场合无法区分  关心订单,真的做到了 需要 反向循环。因此实际上需要另一种结构来准确表达“不关心”的意图,这种意图目前在大多数语言中都不可用,包括ECMAScript,但可以调用,例如, forEachUnordered()

如果订单无关紧要, 效率 是一个问题(在游戏或动画引擎的最里面的循环中),然后使用反向循环作为你的首选模式是可以接受的。请记住,在现有代码中看到反向循环 并不一定意味着 那个订单无关紧要!

最好使用forEach()

通常用于更高级别的代码 清晰和安全 更值得关注,我建议使用 Array::forEach 作为您的默认模式:

  • 很清楚阅读。
  • 它表明 一世 不会在街区内转移(这总是可能会长期隐藏 for 和 while 循环。)
  • 它为您提供了一个免费的闭包范围。
  • 它减少了局部变量的泄漏以及外部变量的意外碰撞(和突变)。

然后,当你在代码中看到反向for循环时,这是一个暗示它被推翻的原因很充分(可能是上述原因之一)。并且看到传统的前向循环可能表明可能发生转移。

(如果对意图的讨论对你没有意义,那么你和你的代码可能会受益于观看Crockford的讲座 编程风格和你的大脑。)


它是如何工作的?

for (var i = 0; i < array.length; i++) { ... }   // Forwards

for (var i = array.length; i--; )    { ... }   // Reverse

你会注意到的 i-- 是中间子句(我们通常会看到比较),最后一个子句是空的(我们通常会看到它) i++)。这意味着 i-- 也被用作 条件 继续。至关重要的是,它被执行并检查 之前 每次迭代。

  • 它怎么能开始 array.length 没有爆炸?

    因为 i-- 运行 之前 每次迭代,在第一次迭代中,我们将实际访问该项目 array.length - 1 这避免了任何问题 数组出界外  undefined 项目。

  • 为什么它不会在索引0之前停止迭代?

    当条件时,循环将停止迭代 i-- 计算为假值(当它产生0时)。

    诀窍是不同的 --i,尾随 i-- 运算符减量 i 但产生了价值 之前 减量。你的控制台可以证明:

    > var i = 5; [i, i--, i];

    [5, 5, 4]

    所以在最后的迭代中, 一世 以前是 1 和 i-- 表达式将其更改为 0 但实际上是收益率 1 (真实的),所以条件通过。在下一次迭代 i-- 变化 一世 至 -1 但收益率 0 (falsey),导致执行立即退出循环的底部。

    在传统的for循环中, i++ 和 ++i 是可以互换的(正如Douglas Crockford指出的那样)。然而在反向for循环中,因为我们的减量也是我们的条件表达式,我们必须坚持 i-- 如果我们想要处理索引0处的项目。


琐事

有些人喜欢在后面画一个小箭头 for 循环,并以眨眼结束:

for (var i = array.length; i --> 0 ;) {

积分转到WYL,向我展示反向循环的好处和恐怖。


88
2018-05-02 14:21



我忘了补充一下 基准。我也忘了提到反向循环如何在像6502这样的8位处理器上进行重大优化,你真的可以免费获得比较! - joeytwiddle
这对于反向循环怎么样? var i = array.length;当我 - ) { ... - Kabb5
对不起@ Kabb5我是AFK。是的,循环非常清晰,基本相同。 - joeytwiddle
啊,好老的“箭头和眨眼”代码模式。我喜欢它!来自我的+1! - Matheus Avellar
相同的答案更加简明扼要 这里 (在另一个问题上)。 - joeytwiddle


一些 C式语言使用 foreach 遍历枚举。在JavaScript中,这是通过 for..in 循环结构

var index,
    value;
for (index in obj) {
    value = obj[index];
}

有一个问题。 for..in 将循环遍历每个对象的可枚举成员以及其原型上的成员。要避免读取通过对象原型继承的值,只需检查属性是否属于该对象:

for (i in obj) {
    if (obj.hasOwnProperty(i)) {
        //do stuff
    }
}

另外, ECMAScript 5 添加了一个 forEach 方法 Array.prototype它可用于使用calback枚举数组(polyfill在文档中,因此您仍然可以将它用于旧版浏览器):

arr.forEach(function (val, index, theArray) {
    //do stuff
});

重要的是要注意到这一点 Array.prototype.forEach 回调返回时不会中断 falsejQuery的 和 Underscore.js 提供自己的变化 each 提供可以短路的循环。


71
2018-02-17 14:00



那么如何突破ECMAScript5 foreach循环,就像我们对普通for循环或者像C风格语言中发现的foreach循环一样? - Ciaran Gallagher
@CiaranG,在JavaScript中很常见 each 允许的方法 return false 用来打破循环,但用 forEach 这不是一个选择。可以使用外部标志(即 if (flag) return;,但它只会阻止函数体的其余部分执行, forEach 仍将继续迭代整个集合。 - zzzzBov


如果要循环遍历数组,请使用标准的三部分 for 循环。

for (var i = 0; i < myArray.length; i++) {
    var arrayItem = myArray[i];
}

您可以通过缓存获得一些性能优化 myArray.length 或者向后迭代它。


30
2018-02-17 13:55



for(var i = 0,length = myArray.length; i <length; i ++)应该这样做 - Edson Medina
@EdsonMedina这也将创建一个名为的新全局变量 length。 ;) - joeytwiddle
@joeytwiddle是的,但这超出了这篇文章的范围。无论如何,你将创建一个全局变量。 - Edson Medina
@EdsonMedina我道歉,我完全错了。运用 ,在任务完成后 不 介绍一个新的全球,所以你的建议是公正的 精细!我对这个问题感到困惑:使用 = 转让后 确实创造了 一个新的全球化。 - joeytwiddle
当心变量我是 不 循环本地。 JavaScript没有块范围。声明可能更好 var i, length, arrayItem; 在循环之前避免这种误解。 - James


一个 的forEach 实施(在jsFiddle看到):

function forEach(list,callback) {
  var length = list.length;
  for (var n = 0; n < length; n++) {
    callback.call(list[n]);
  }
}

var myArray = ['hello','world'];

forEach(
  myArray,
  function(){
    alert(this); // do something
  }
);

25
2018-04-10 00:26



迭代器在这里,正在做一个不必要的长度计算。在理想情况下,列表长度应仅计算一次。 - MIdhun Krishna
@MIdhunKrishna我更新了我的答案和jsFiddle,但要注意它并不像你想象的那么简单。检查一下 题 - nmoliveira
完整和正确的实现可以在这里找到: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - marciowb


如果你不介意清空数组:

var x;

while(x = y.pop()){ 

    alert(x); //do something 

}

x 将包含最后一个值 y 它将从数组中删除。你也可以使用 shift() 这将提供和删除第一项 y


25
2018-03-10 02:37



如果碰巧有一个稀疏数组,它就不起作用 [1, 2, undefined, 3]。 - M. Grzywaczewski
......或者确实是虚假的: [1, 2, 0, 3] 要么 [true, true, false, true] - joeytwiddle


我知道这是一个老帖子,已经有很多很棒的答案了。为了更完整一点,我想我会再使用另一个 AngularJS。当然,这只适用于你使用Angular的情况,显然,无论如何我还是想把它放进去。

angular.forEach 需要2个参数和一个可选的第三个参数。第一个参数是迭代的对象(数组),第二个参数是迭代器函数,可选的第三个参数是对象上下文(在循环内部基本上称为'this'。

有不同的方法可以使用forEach循环的角度。最简单也可能最常用的是

var temp = [1, 2, 3];
angular.forEach(temp, function(item) {
    //item will be each element in the array
    //do something
});

另一种将项目从一个数组复制到另一个数组的方法是

var temp = [1, 2, 3];
var temp2 = [];
angular.forEach(temp, function(item) {
    this.push(item); //"this" refers to the array passed into the optional third parameter so, in this case, temp2.
}, temp2);

虽然,您不必这样做,但您可以简单地执行以下操作,它与上一个示例相同:

angular.forEach(temp, function(item) {
    temp2.push(item);
});

现在有使用的优点和缺点 angular.forEach 功能与内置香草味相反 for 循环。

优点

  • 易读性
  • 易写性
  • 如果可供使用的话, angular.forEach 将使用ES5 forEach循环。现在,我将在cons部分中获得效率,就像forEach循环一样 许多 比for循环慢。我作为专业人士提到这一点,因为保持一致和标准化是件好事。

考虑以下2个嵌套循环,这些循环完全相同。假设我们有2个对象数组,每个对象包含一个结果数组,每个结果都有一个Value属性,它是一个字符串(或其他)。并且假设我们需要迭代每个结果,如果它们相等则执行一些操作:

angular.forEach(obj1.results, function(result1) {
    angular.forEach(obj2.results, function(result2) {
        if (result1.Value === result2.Value) {
            //do something
        }
    });
});

//exact same with a for loop
for (var i = 0; i < obj1.results.length; i++) {
    for (var j = 0; j < obj2.results.length; j++) {
        if (obj1.results[i].Value === obj2.results[j].Value) {
            //do something
        }
    }
}

虽然这是一个非常简单的假设示例,但我已经使用第二种方法编写了三次嵌入for循环,它就是 非常 难以阅读,并写这件事。

缺点

  • 效率。 angular.forEach和原生的 forEach就此而言,两者都是 非常 比正常慢 for 环....约 慢了90%。因此对于大型数据集,最好坚持原生 for 循环。
  • 没有休息,继续或返回支持。 continue 实际上是支持“事故“,继续 angular.forEach 你简单的把一个 return; 函数中的语句就像 angular.forEach(array, function(item) { if (someConditionIsTrue) return; }); 这会导致它继续执行该迭代的功能。这也是由于本机的事实 forEach 不支持中断或继续。

我相信还有其他各种优点和缺点,请随意添加任何你认为合适的东西。我觉得,如果你需要效率的话,最重要的是坚持本土 for 循环需要你的循环。但是,如果你的数据集较小并且可以放弃一些效率以换取可读性和可写性,那么无论如何都要扔掉一个 angular.forEach 在那个坏男孩。


25
2018-06-20 22:56





现在一个简单的解决方案是使用 underscore.js库。它提供了许多有用的工具,例如 each 并将自动将作业委托给本机 forEach 如果可供使用的话。

CodePen示例 它的工作原理是:

var arr = ["elemA", "elemB", "elemC"];
_.each(arr, function(elem, index, ar)
{
...
});

也可以看看


24
2017-07-17 09:07





可能是 for(i = 0; i < array.length; i++) 循环不是最好的选择。为什么?如果你有这个:

var array = new Array();
array[1] = "Hello";
array[7] = "World";
array[11] = "!";

该方法将调用 array[0] 至 array[2]。首先,这将首先引用你甚至没有的变量,第二个你不会在数组中有变量,第三个会使代码更大胆。看这里,这是我使用的:

for(var i in array){
    var el = array[i];
    //If you want 'i' to be INT just put parseInt(i)
    //Do something with el
}

如果你想让它成为一个函数,你可以这样做:

function foreach(array, call){
    for(var i in array){
        call(array[i]);
    }
}

如果你想打破,更多的逻辑:

function foreach(array, call){
    for(var i in array){
        if(call(array[i]) == false){
            break;
        }
    }
}

例:

foreach(array, function(el){
    if(el != "!"){
        console.log(el);
    } else {
        console.log(el+"!!");
    }
});

它返回:

//Hello
//World
//!!!

21
2017-11-02 02:23