如何使用JavaScript循环遍历数组中的所有条目?
我以为它是这样的:
forEach(instance in theArray)
哪里 theArray
是我的阵列,但这似乎是不正确的。
如何使用JavaScript循环遍历数组中的所有条目?
我以为它是这样的:
forEach(instance in theArray)
哪里 theArray
是我的阵列,但这似乎是不正确的。
TL; DR
for-in
除非你将它与保护措施一起使用,或者至少知道它为什么会咬你。你最好的赌注通常是
但是有 地段 更多探索,继续阅读......
JavaScript具有强大的语义,可以循环遍历数组和类似数组的对象。我把答案分成两部分:正版数组的选项,以及只是数组的选项 - 喜欢, 如那个 arguments
对象,其他可迭代对象(ES2015 +),DOM集合等。
我很快就会注意到您可以使用ES2015选项 现在,甚至在ES5引擎上,通过 transpiling ES2015到ES5。搜索“ES2015 transpiling”/“ES6 transpiling”了解更多...
好的,让我们来看看我们的选择:
你有三个选择 ECMAScript的5 (“ES5”),目前最广泛支持的版本,还有两个版本 2015年的ECMAScript (“ES2015”,“ES6”):
forEach
和相关(ES5 +)for
循环for-in
正确地for-of
(隐式使用迭代器)(ES2015 +)细节:
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
,但按降序而不是按升序排列)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
。
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]);
}
}
请注意两个检查:
对象有它的 拥有 该名称的属性(不是它从原型继承的那个),和
密钥是普通字符串形式的基数为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]);
}
}
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中,访问条目的顺序是其索引的数字顺序。
有时,您可能希望使用迭代器 明确地。你也可以做到这一点,虽然它比笨笨更多 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
是可选的,如果是的话 false
, value
是可选的,如果是的话 undefined
。)
的意思 value
取决于迭代器;数组支持(至少)三个返回迭代器的函数:
values()
:这是我上面使用的那个。它返回一个迭代器,每个迭代器 value
是该迭代的数组条目("a"
, "b"
,和 "c"
在前面的例子中)。keys()
:返回每个迭代器 value
是那次迭代的关键(所以我们的 a
以上,那就是 "0"
, 然后 "1"
, 然后 "2"
)。entries()
:返回每个迭代器 value
是表单中的数组 [key, value]
对于那次迭代。除了真正的数组,还有 阵列状 有一个的对象 length
具有数字名称的属性和属性: NodeList
实例, arguments
对象等。我们如何循环其内容?
上面的阵列方法中的至少一些,可能是大多数甚至全部,经常同样适用于类似数组的对象:
使用 forEach
和相关(ES5 +)
各种功能 Array.prototype
是“故意通用的”,通常可以用于类似数组的对象 Function#call
要么 Function#apply
。 (见 警告主机提供的对象 在这个答案的最后,但这是一个罕见的问题。)
假设你想使用 forEach
在...上 Node
的 childNodes
属性。你这样做:
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`
});
用一个简单的 for
循环
显然,简单 for
loop适用于类似数组的对象。
使用 for-in
正确地
for-in
使用与数组相同的安全措施也应该使用类似数组的对象;上面#1上主机提供的对象的警告可能适用。
使用 for-of
(隐式使用迭代器)(ES2015 +)
for-of
将使用对象提供的迭代器(如果有的话);我们将不得不看到它如何与各种类似数组的对象一起使用,特别是主机提供的对象。例如,规范 NodeList
从 querySelectorAll
已更新以支持迭代。该规范 HTMLCollection
从 getElementsByTagName
不是。
明确使用迭代器(ES2015 +)
看到#4,我们必须看看迭代器是如何发挥作用的。
其他时候,您可能希望将类似数组的对象转换为真正的数组。这样做非常简单:
使用 slice
数组方法
我们可以使用 slice
数组的方法,与上面提到的其他方法一样,是“故意通用的”,因此可以与类似数组的对象一起使用,如下所示:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
所以,例如,如果我们想转换一个 NodeList
进入一个真正的数组,我们可以做到这一点:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
见 警告主机提供的对象 下面。特别要注意的是,这将在IE8及更早版本中失败,因为它不允许您使用主机提供的对象 this
像那样。
使用 传播语法(...
)
也可以使用ES2015 传播语法 使用支持此功能的JavaScript引擎:
var trueArray = [...iterableObject];
所以,例如,如果我们想转换一个 NodeList
进入一个真正的数组,使用扩展语法,这变得非常简洁:
var divs = [...document.querySelectorAll("div")];
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]]
正确,但测试很重要。)
编辑:这个答案毫无希望地过时了。如需更现代的方法,请查看 数组上可用的方法。感兴趣的方法可能是:
迭代数组的标准方法 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。
如果你正在使用 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].
}
我觉得 相反 for循环值得一提:
for (var i = array.length; i--; ) {
// process array[i]
}
len
变量,或比较 array.length
在每次迭代中,其中任何一个都可能是一个小时优化。array[i]
),然后前进循环将跳过向左移动到位的项目 一世,或重新处理 一世那个向右移动的物品。在传统的for循环中,你 可以 更新 一世 指向需要处理的下一个项目 - 1,但简单地反转迭代方向通常是一个 简单 和 更优雅的解决方案。forEach()
和ES6的 for ... of
。一些开发人员使用reverse for循环 默认,除非有充分的理由继续前进。
虽然性能提升通常微不足道,但它有点尖叫:
“只需对列表中的每个项目执行此操作,我不关心订单!”
然而在实践中 不 实际上是意图的可靠指示,因为它与你的那些场合无法区分 做 关心订单,真的做到了 需要 反向循环。因此实际上需要另一种结构来准确表达“不关心”的意图,这种意图目前在大多数语言中都不可用,包括ECMAScript,但可以调用,例如, forEachUnordered()
。
如果订单无关紧要, 效率 是一个问题(在游戏或动画引擎的最里面的循环中),然后使用反向循环作为你的首选模式是可以接受的。请记住,在现有代码中看到反向循环 并不一定意味着 那个订单无关紧要!
通常用于更高级别的代码 清晰和安全 更值得关注,我建议使用 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,向我展示反向循环的好处和恐怖。
一些 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
回调返回时不会中断 false
。 jQuery的 和 Underscore.js 提供自己的变化 each
提供可以短路的循环。
如果要循环遍历数组,请使用标准的三部分 for
循环。
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
您可以通过缓存获得一些性能优化 myArray.length
或者向后迭代它。
一个 的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
}
);
如果你不介意清空数组:
var x;
while(x = y.pop()){
alert(x); //do something
}
x
将包含最后一个值 y
它将从数组中删除。你也可以使用 shift()
这将提供和删除第一项 y
。
我知道这是一个老帖子,已经有很多很棒的答案了。为了更完整一点,我想我会再使用另一个 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
在那个坏男孩。
现在一个简单的解决方案是使用 underscore.js库。它提供了许多有用的工具,例如 each
并将自动将作业委托给本机 forEach
如果可供使用的话。
CodePen示例 它的工作原理是:
var arr = ["elemA", "elemB", "elemC"];
_.each(arr, function(elem, index, ar)
{
...
});
Array.prototype.forEach()
。for each (variable in object)
被弃用为ECMA-357的一部分(EAX)标准。for (variable of object)
作为和谐(ECMAScript 6)提案的一部分。可能是 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
//!!!