如何使用 JavaScript 循环遍历数组中的所有条目?
我以为它是这样的:
forEach(instance in theArray)
其中theArray
是我的数组,但这似乎是不正确的。
TL; DR
for-in
否则不要使用for-in
。 通常你最好的赌注
但是,还有其它更多的探索,阅读...
JavaScript 具有强大的语义,可以循环遍历数组和类似数组的对象。我将答案分为两部分:正版数组的选项,以及类似数组的选项,例如arguments
对象,其他可迭代对象(ES2015 +),DOM 集合等。
我很快就会注意到,你现在可以使用 ES2015 的选择,即使是在 ES5 引擎,通过transpiling ES2015 到 ES5。搜索 “ES2015 transpiling”/“ES6 transpiling” 了解更多...
好的,让我们来看看我们的选择:
ECMAScript 5 (“ES5”)中有三个选项,目前支持最广泛的版本,以及ECMAScript 2015 中添加的两个选项(“ES2015”,“ES6”):
forEach
和相关(ES5 +) for
循环for-in
for-of
(隐式使用迭代器)(ES2015 +) 细节:
forEach
和相关在任何模糊的现代环境(因此,不是 IE8)中,您可以访问由 ES5 添加的Array
功能(直接或使用 polyfill),您可以使用forEach
( spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
接受一个回调函数和任选一个值,以作为使用this
调用该回调(以上未使用)时。为数组中的每个条目调用回调,按顺序跳过稀疏数组中不存在的条目。虽然我上面只使用了一个参数,但回调是用三个调用的:每个条目的值,该条目的索引,以及对你要迭代的数组的引用(如果你的函数还没有方便的话) )。
除非您支持 IE8 等过时的浏览器(截至 2016 年 9 月,NetApps 的市场份额仅超过 4%),您可以愉快地在没有垫片的通用网页中使用forEach
。如果你需要支持过时的浏览器,匀场 / polyfilling forEach
是很容易做到(搜索 “ES5 垫片” 几个选项)。
forEach
的好处是您不必在包含作用域中声明索引和值变量,因为它们作为参数提供给迭代函数,因此很好地限定了该迭代。
如果您担心为每个数组条目调用函数的运行时成本,请不要这样做; 细节 。
另外, forEach
是 “循环通过它们” 的功能,但 ES5 定义了其他一些有用的 “按照你的方式通过数组并做事” 的功能,包括:
every
(在第一次回调返回false
或 falsey 时停止循环) some
(第一次回调返回true
时停止循环或者 truthy) filter
(创建一个新数组,包括 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 位无符号整数。) (Props to 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);
}
输出:
a b c
在封面下,它从数组中获取一个迭代器并循环遍历它,从中获取值。这没有使用for-in
问题,因为它使用由对象(数组)定义的迭代器,并且数组定义它们的迭代器遍历它们的条目 (而不是它们的属性)。与 ES5 中的for-in
不同for-in
访问条目的顺序是其索引的数字顺序。
有时,您可能希望显式使用迭代器。你可以这样做,太,虽然它比笨重了很多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
。 (如果它是false
,则done
是可选的,如果undefined
,则value
是可选的。)
value
的含义取决于迭代器; 数组支持(至少)三个返回迭代器的函数:
values()
:这是我上面使用的那个。它返回一个迭代器,其中每个value
都是该迭代的数组条目(前面示例中的"a"
, "b"
和"c"
)。 keys()
:返回一个迭代器,其中每个value
都是该迭代的关键(因此,对于我们a
上面,那将是"0"
,然后是"1"
,然后是"2"
)。 entries()
:返回一个迭代器,其中每个value
都是该迭代形式[key, value]
的数组。 除了真正的数组之外,还有类似数组的对象,它们具有length
属性和带有数字名称的属性: NodeList
实例, arguments
对象等。我们如何循环其内容?
上面的阵列方法中的至少一些,可能是大多数甚至全部,经常同样适用于类似数组的对象:
Use forEach
and related (ES5+)
The various functions on Array.prototype
are "intentionally generic" and can usually be used on array-like objects via Function#call
or Function#apply
. (See the Caveat for host-provided objects at the end of this answer, but it's a rare issue.)
Suppose you wanted to use forEach
on a Node
's childNodes
property. You'd do this:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
If you're going to do that a lot, you might want to grab a copy of the function reference into a variable for reuse, e.g.:
// (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`
});
Use a simple for
loop
Obviously, a simple for
loop applies to array-like objects.
Use for-in
correctly
for-in
with the same safeguards as with an array should work with array-like objects as well; the caveat for host-provided objects on #1 above may apply.
Use for-of
(use an iterator implicitly) (ES2015+)
for-of
will use the iterator provided by the object (if any); we'll have to see how this plays with the various array-like objects, particularly host-provided ones. For instance, the specification for the NodeList
from querySelectorAll
was updated to support iteration. The spec for the HTMLCollection
from getElementsByTagName
was not.
Use an iterator explicitly (ES2015+)
See #4, we'll have to see how iterators play out.
Other times, you may want to convert an array-like object into a true array. Doing that is surprisingly easy:
Use the slice
method of arrays
We can use the slice
method of arrays, which like the other methods mentioned above is "intentionally generic" and so can be used with array-like objects, like this:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
So for instance, if we want to convert a NodeList
into a true array, we could do this:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
See the Caveat for host-provided objects below. In particular, note that this will fail in IE8 and earlier, which don't let you use host-provided objects as this
like that.
It's also possible to use ES2015's spread syntax with JavaScript engines that support this feature:
var trueArray = [...iterableObject];
So for instance, if we want to convert a NodeList
into a true array, with spread syntax this becomes quite succinct:
var divs = [...document.querySelectorAll("div")];
Array.from
(ES2015+, but easily polyfilled) creates an array from an array-like object, optionally passing the entries through a mapping function first. So:
var divs = Array.from(document.querySelectorAll("div"));
Or if you wanted to get an array of the tag names of the elements with a given class, you'd use the mapping function:
// 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;
});
If you use Array.prototype
functions with host-provided array-like objects (DOM lists and other things provided by the browser rather than the JavaScript engine), you need to be sure to test in your target environments to make sure the host-provided object behaves properly. Most do behave properly (now), but it's important to test. The reason is that most of the Array.prototype
methods you're likely to want to use rely on the host-provided object giving an honest answer to the abstract [[HasProperty]]
operation. As of this writing, browsers do a very good job of this, but the 5.1 spec did allow for the possibility a host-provided object may not be honest. It's in §8.6.2, several paragraphs below the big table near the beginning of that section), where it says:
Host objects may implement these internal methods in any manner unless specified otherwise; for example, one possibility is that
[[Get]]
and[[Put]]
for a particular host object indeed fetch and store property values but[[HasProperty]]
always generates false.
(I couldn't find the equivalent verbiage in the ES2015 spec, but it's bound to still be the case.) Again, as of this writing the common host-provided array-like objects in modern browsers [NodeList
instances, for instance] do handle [[HasProperty]]
correctly, but it's important to test.)
编辑 :这个答案毫无希望地过时了。有关更现代的方法,请查看阵列上可用的方法 。感兴趣的方法可能是:
迭代以阵列的标准方法的 JavaScript是香草for
-loop:
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 方法,但在旧版浏览器中不支持它。因此,为了能够始终如一地使用它,您必须具有支持它的环境(例如,服务器端 JavaScript 的Node.js ),或使用 “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].
}