如何用JavaScript循环浏览一个数组中的所有条目?
我以为是这样的。
forEach(instance in theArray)
其中theArray
是我的数组,但这似乎是不正确的。
我们非常感谢你的编辑,这些年来已经有好几个好编辑了。遗憾的是,这些编辑的数量远远超过了我不得不拒绝(如果他们被保留审查)或退回(如果没有)的相当差的编辑。 求你了,求你了。
--> TL;DR *不要使用 "for-in",除非你在使用它时有保障措施,或者至少知道为什么它可能会咬你。
for-of
循环(仅ES2015+)。Array#forEach
([spec
][1] | [MDN
][2]) (或其相关的some
等) (仅ES5+)。for
循环。
*或有保障的for-in
。
但还有***多的东西要探索,请继续阅读......JavaScript有强大的语义,可以通过数组和类似数组的对象进行循环。我把答案分成两部分。真正的数组的选项,以及类似数组的东西的选项,例如arguments
对象、其他可迭代对象(ES2015+)、DOM集合等等。
我很快就会注意到,你现在就可以使用ES2015的选项,甚至在ES5引擎上,通过transpilingES2015到ES5。搜索"ES2015转译"/"ES6转译"了解更多。
好吧,让我们看看我们的选择。
在[ECMAScript 5][3]("ES5")中你有三个选择,这是目前最广泛支持的版本,在[ECMAScript 2015][4]中又增加了两个("ES2015", "ES6")。
1.使用forEach
和相关的(ES5+)。
2.使用一个简单的for
循环
3.正确地使用`for-in'*。
4.使用 "for-of"(隐式使用一个迭代器)(ES2015+)。
5.明确地使用迭代器 (ES2015+)
详细信息。
forEach
和相关的在任何模糊的现代环境中(所以,不是IE8),如果你能访问ES5增加的Array
功能(直接或使用polyfills),你可以使用forEach
([spec
][1] | [MDN
][2])。
<! -- begin snippet: js hide: false console: true babel: false -->
forEach
接受一个回调函数,也可以选择一个值,在调用该回调时作为this
使用(上面没有使用)。回调函数会按顺序对数组中的每个条目进行调用,在稀疏的数组中跳过不存在的条目。虽然我在上面只用了一个参数,但回调是用三个参数调用的。每个条目的值,该条目的索引,以及对你正在迭代的数组的引用(以防你的函数还没有掌握它)。
除非你支持过时的浏览器,如IE8(NetApps显示,截至2016年9月写这篇文章时,IE8的市场份额刚刚超过4%),否则你可以很高兴地在通用网页中使用forEach
,而不需要使用垫片。如果你确实需要支持过时的浏览器,对forEach'进行修饰/填充很容易做到(搜索"es5 shim"有几个选项)。
forEach'的好处是,你不必在包含的范围内声明索引和值变量,因为它们是作为迭代函数的参数提供的,所以很好地限定了该迭代的范围。
如果你担心为每个数组条目进行函数调用的运行时间成本,不要担心;[详情](http://blog.niftysnippets.org/2012/02/foreach-and-runtime-cost.html)。
此外,`forEach'是"循环浏览它们"的函数,但ES5定义了其他几个有用的"在数组中工作并做事情"的函数,包括。
every
][5] (在回调第一次返回false
或其他错误时停止循环)some
][6] (在回调第一次返回 "true "或其他真实的东西时停止循环)filter
][7] (创建一个新的数组,包括过滤函数返回 "true "的元素,省略返回 "false "的元素)map
][8] (从回调返回的值中创建一个新的数组)reduce
][9] (通过重复调用回调函数建立一个值,传入之前的值;详情见规范;对数组内容的求和及其他许多事情很有用)reduceRight
][10] (像reduce
,但以降序而非升序工作)for
循环有时老方法是最好的。 <! -- begin snippet: js hide: false console: true babel: false -->
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, value);
}
//console.log(index); // would cause "ReferenceError: index is not defined"
//console.log(value); // would cause "ReferenceError: value is not defined"
<! -- begin snippet: js hide: true console: true babel: false -->
当你这样做的时候,不仅仅是value
,还有index
在每个循环迭代中都会被重新创建,这意味着在循环体中创建的闭包会保持对为该特定迭代创建的index
(和value
)的引用。
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
如果你有五个div,你会得到"索引是。0",如果你点击了第一个,而"索引是。4",如果你点击了最后一个。如果你使用var
而不是let
,这就不能***工作。
有人会告诉你使用 "for-in",但 "for-in "的作用不是这样的[11]。for-in'循环对象的*可数属性*,而不是数组的索引。**顺序是不保证的**,甚至在ES2015(ES6)中也是如此。ES2015+确实定义了对象属性的顺序(通过[
[[OwnPropertyKeys]]](https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys), `[[Enumerate]], 以及像[Object.getOwnPropertyKeys
][12]这样使用它们的东西),但它**没有定义for-in
将遵循这个顺序。(详情见[其他答案][13])。
`for-in'在数组上的唯一真正用处是。
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 checks are
/^0$|^[1-9]\d*$/.test(key) && // explained
key <= 4294967294 // below
) {
console.log(a[key]);
}
}
注意这三个检查。 1.该对象有其自己的该名称的属性(而不是从其原型继承的属性),以及 2.2. 键是所有的十进制数字(例如,正常的字符串形式,而不是科学符号),以及 3.该键的值在被强制转换成数字时是<=2^32-2(即4,294,967,294)。这个数字是怎么来的?它是[规范中]数组索引定义的一部分(https://tc39.github.io/ecma262/#array-index)。其他数字(非整数、负数、大于2^32-2的数字)不是数组索引。之所以是2^32 - 2,是因为这使得最大的索引值比2^32 - 1低一个,这是一个数组的 "长度 "可以有的最大值。(例如,一个数组的长度适合于32位无符号整数。) (感谢RobG在我的博文的评论中指出我之前的测试不是很正确。) 当然,你不会在内联代码中这样做。你会写一个实用函数。也许。
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
for-of
(隐式使用一个迭代器)(ES2015+)。ES2015在JavaScript中加入了iterators。使用迭代器的最简单方法是新的for-of
语句。它看起来像这样。
<! -- begin snippet: js hide: false console: true babel: false -->
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
掩耳盗铃,从数组中获得一个iterator,并在其中循环,从中获取数值。这没有使用for-in
的问题,因为它使用对象(数组)定义的迭代器,而数组定义的迭代器是通过其entries(而不是其属性)进行迭代。与ES5中的for-in
不同,访问条目的顺序是其索引的数字顺序。
有时,你可能想*显式地使用一个迭代器。你也可以这样做,尽管它比for-of
要复杂得多。它看起来像这样。
const a = ["a", "b", "c"];
const it = a.values();
let 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
对象,等等。我们如何循环浏览它们的内容?
上面的数组方法中,至少有一部分,可能还有大部分甚至全部,经常同样适用于类数组对象。
1.使用forEach
和相关的(ES5+)。
Array.prototype
上的各种函数都是"有意的通用",通常可以通过[Function#call
][15]或[Function#apply
][16]用于类数组对象。(参见本答案末尾的宿主提供的对象的注意事项,但这是一个罕见的问题)。
假设你想在一个 "节点 "的 "子节点 "属性上使用 "forEach"。你要这样做。
Array.prototype.forEach.call(node.childNodes, function(child) {
//对child
做一些事情。
});
如果你要经常这样做,你可能想把函数引用的副本抓到一个变量中,以便重复使用,例如。
// (这大概都是在某个范围内的函数)
var forEach = Array.prototype.forEach;
// 然后再...
forEach.call(node.childNodes, function(child) {
// 对child
做一些事情
});
2.使用一个简单的for
循环。
很明显,一个简单的for
循环适用于类似数组的对象。
3.3. **正确使用for-in
*.
`for-in'与数组的保障措施相同,应该也适用于类数组对象;上面第1条中关于主机提供的对象的注意事项可能适用。
4.4. 使用for-of
(隐式使用迭代器)(ES2015+)
for-of
将使用对象提供的迭代器(如果有的话);我们必须看看这对各种类似数组的对象,特别是对主机提供的对象的作用。例如,"querySelectorAll "的 "NodeList "的规范已经更新,以支持迭代。而 "getElementsByTagName "的 "HTMLCollection "的规范则没有。
5.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.使用spread syntax (...
)。
也可以使用ES2015'的spread syntax与支持此功能的JavaScript引擎。
var trueArray = [...iterableObject]。
因此,举例来说,如果我们想把一个NodeList
转换成一个真正的数组,用spread语法,这就变得非常简洁了。
var divs = [...document.querySelectorAll("div")] 。
3.使用Array.from
[(spec)][17] | [(MDN)][18] 。
Array.from
(ES2015+, but easily polyfilled) 从一个类似数组的对象中创建一个数组,可以选择先通过一个映射函数传递条目。所以。
var divs = Array.from(document.querySelectorAll("div")) 。
或者,如果你想得到一个具有给定类别的元素的标签名称的数组,你会使用映射函数。
// Arrow函数(ES2015)。
var divs = Array.from(document.querySelectorAll(".site-class"), element => element.tagName)。
// 标准函数(因为Array.from
可以被修饰)。
var divs = Array.from(document.querySelectorAll(".me-class"), function(element) {
return element.tagName;
});
如果你用Array.prototype
函数来处理*主机提供的类似数组的对象(DOM列表和其他由浏览器而不是JavaScript引擎提供的东西),你需要确保在目标环境中进行测试,以确保主机提供的对象行为正常。大多数的行为都是正常的(现在),但测试是很重要的。原因是大多数你可能想要使用的Array.prototype
方法都依赖于主机提供的对象对抽象的[[[HasProperty]]
[19]操作给出诚实的回答。截至目前,浏览器在这方面做得非常好,但5.1规范确实允许主机提供的对象可能不诚实。在§8.6.2中,在该节开头附近的大表格下面有几段话),它说。
除非另有规定,否则主机对象可以以任何方式实现这些内部方法;例如,一种可能性是,某个主机对象的
[[Get]]和
[[Put]]的确可以获取和存储属性值,但[[HasProperty]]总是生成**false**。 (我在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
}
然而,请注意,这种方法只有在你有一个密集的数组,并且每个索引都被一个元素占据的情况下才是好的。如果数组是稀疏的,那么这种方法就会遇到性能问题,因为你会在大量的索引上进行迭代,而这些索引在数组中并不存在。在这种情况下,一个 "for ... in "的循环可能是一个更好的主意。然而**,你必须使用适当的保障措施,以确保只对数组的预期属性(即数组元素)采取行动,因为for...in
-循环在传统的浏览器中也会被枚举,或者如果附加属性被定义为enumerable
。
在ECMAScript 5中,数组原型上会有一个forEach方法,但在传统的浏览器中不支持。所以为了能够持续使用它,你必须有一个支持它的环境(例如,Node.js的服务器端JavaScript),或者使用一个"Polyfill"。然而,这个功能的Polyfill是微不足道的,由于它使代码更容易阅读,所以它是一个很好的Polyfill,包括。