JavaScript中循环遍历数组
JavaScript中循环遍历数组
在Java中,您可以使用for
循环遍历数组中的对象,如下所示:
String[] myStringArray = {"Hello", "World"}; for (String s : myStringArray) { // Do something }
在JavaScript中,我也能这样做吗?
是的,假设您的实现包括在for
...of
功能中引入的ECMAScript 2015(“Harmony”版本)......这是目前一个非常安全的假设。
它的工作原理如下:
// REQUIRES ECMASCRIPT 2015+ var s, myStringArray = ["Hello", "World"]; for (s of myStringArray) { // ... do something with s ... }
或者更好的做法,由于ECMAScript 2015还提供了块级作用域变量:
// REQUIRES ECMASCRIPT 2015+ const myStringArray = ["Hello", "World"]; for (const s of myStringArray) { // ... do something with s ... } // s is no longer defined here
(变量s在每次迭代中都不同,但只要它不在那里被修改,就可以在循环体内声明为const
。)
关于稀疏数组的说明:在JavaScript中,数组实际上可能没有存储与其length
报告的一样多的项;该数字仅仅比存储值的最高索引多1。如果数组中存储的元素比其长度指示的少,则它被认为是稀疏的。例如,一个只在索引3、12和247处拥有项的数组是完全合法的;这样一个数组的length
是248,尽管它只实际存储了3个值。如果您尝试访问任何其他索引处的项目,数组将看起来在那里具有undefined
值,但数组仍然不同于实际上存储了undefined
值的数组。您可以通过许多方式看到这种差异,例如在Node REPL中显示数组的方式:
> a // array with only one item, at index 12 [ <12 empty items>, 1 ] > a[0] // appears to have undefined at index 0 undefined > a[0]=undefined // but if we put an actual undefined there undefined > a // it now looks like this [ undefined, <11 empty items>, 1 ]
因此,当您想要“循环遍历”数组时,您需要回答一个问题:您想循环处理长度所示的完整范围,并处理任何丢失元素的 undefined
,还是只想处理实际存在的元素?这两种方法都有很多应用;这取决于您使用数组的用途。
如果您使用 for
..of
遍历数组,则循环体将执行 length
次,对于任何未实际存在于数组中的项,循环控制变量将设置为 undefined
。根据您“处理某些内容”的代码的细节,该行为可能是您想要的,但如果不是,您应该使用其他方法。
当然,某些开发人员无论如何都不得不使用不同的方法,因为出于某种原因,他们的目标是尚不支持 for
...of
的 JavaScript 版本。
只要您的 JavaScript 实现符合上一版 ECMAScript 规范(例如,在 Internet Explorer 9 之前的版本中使用该规范是不被支持的),那么您可以使用 Array#forEach
迭代器方法来代替循环。在这种情况下,您需要传递一个函数,以便在数组中的每个项上调用该函数:
var myStringArray = [ "Hello", "World" ]; myStringArray.forEach( function(s) { // ... do something with s ... } );
如果您的实现支持 ES6+,当然可以使用箭头函数:
myStringArray.forEach( s => { // ... do something with s ... } );
与 for
...of
不同,.forEach
仅对数组中实际存在的元素调用该函数。如果传入了一个具有三个元素和长度为 248 的虚拟数组,则它仅会调用三次该函数,而不是 248 次。如果这是您想要处理稀疏数组的方式,那么即使您的解释器支持 for
...of
,使用 .forEach
也可能是一种方法。
最后一个选项适用于所有 JavaScript 的版本,即显式计数循环。您只需从0计数到长度减1,然后使用计数器作为索引即可。基本循环如下:
var i, s, myStringArray = [ "Hello", "World" ], len = myStringArray.length; for (i=0; i
这种方法的一个优点是您可以选择如何处理稀疏数组。上面的代码将在完整的
length
次循环体内运行,对于任何缺失的元素,s
都设置为undefined
,就像for
..of
;如果您希望处理稀疏数组中实际存在的元素,就像.forEach
,您可以在索引上添加一个简单的in
测试:var i, s, myStringArray = [ "Hello", "World" ], len = myStringArray.length; for (i=0; i
根据您的实现的优化,将长度值分配给局部变量(而不是在循环条件中包含完整的
myStringArray.length
表达式)可能在性能上产生重大影响,因为它可以跳过每次属性查找。您可能会在循环初始化子句中看到长度缓存,例如:var i, len, myStringArray = [ "Hello", "World" ]; for (len = myStringArray.length, i=0; i
显式计数循环意味着你可以访问每个值的索引,如果你需要的话。索引也作为额外参数传递给传递给
forEach
的函数,所以你也可以通过这种方式访问它:myStringArray.forEach( (s,i) => { // ... do something with s and i ... });
for
...of
不会给你每个对象关联的索引,但只要你正在迭代的对象实际上是Array
的一个实例(而不是for
..of
适用于的其他可迭代类型之一),你可以使用Array#entries方法将其转换为一个[index,item]对的数组,然后对其进行迭代:for (const [i, s] of myStringArray.entries()) { // ... do something with s and i ... }
其他人提到的
for
...in
语法用于循环遍历对象的属性;由于JavaScript中的数组只是具有数值属性名称(和自动更新的length
属性)的对象,理论上你可以使用它循环遍历数组。但问题在于它不仅限于数值属性值(记住,甚至方法实际上只是值为闭包的属性),而且不能保证按数值顺序进行迭代。因此,for
...in
语法不应用于循环遍历数组。
三种主要的选项:
for (var i = 0; i < xs.length; i++) { console.log(xs[i]); }
xs.forEach((x, i) => console.log(x));
for (const x of xs) { console.log(x); }
详细的示例在下面。
1. 顺序for
循环:
var myStringArray = ["Hello","World"]; var arrayLength = myStringArray.length; for (var i = 0; i < arrayLength; i++) { console.log(myStringArray[i]); //Do something }
优点:
- 适用于所有环境
- 可以使用
break
和continue
流程控制语句
缺点:
- 过于冗长
- 命令式的
- 容易出现越界错误(有时也称为围栏贴错误)
2. Array.prototype.forEach
:
ES5规范引入了很多有益的数组方法之一是Array.prototype.forEach
,它为我们提供了一种简洁的迭代数组的方式:
const array = ["one", "two", "three"] array.forEach(function (item, index) { console.log(item, index); });
到写这篇文章时距离ES5规范发布已经将近十年(2009年12月),几乎所有现代引擎在桌面、服务器和移动环境中都已经实现了它,因此可以放心使用。
而且使用ES6箭头函数语法,代码更加简洁:
array.forEach(item => console.log(item));
箭头函数也被广泛实现,除非你打算支持古老的平台(例如Internet Explorer 11)。
优点
- 非常短小精悍
- 声明式
缺点
- 无法使用break/continue
通常,可以通过在迭代元素之前对数组元素进行过滤来替代需要从命令式循环中跳出的需要。
array.filter(item => item.condition < 10) .forEach(item => console.log(item))
请记住,如果您要迭代一个数组以构建另一个数组,则应该使用map
。我已经看到这种反模式很多次了。
反模式:
const numbers = [1,2,3,4,5], doubled = []; numbers.forEach((n, i) => { doubled[i] = n * 2 });
map
的正确用法:
const numbers = [1,2,3,4,5]; const doubled = numbers.map(n => n * 2); console.log(doubled);
此外,如果您要将数组减少为一个值,例如,您想要对数字数组求和,则应使用reduce
方法。
反模式:
const numbers = [1,2,3,4,5]; const sum = 0; numbers.forEach(num => { sum += num });
reduce
的正确用法:
const numbers = [1,2,3,4,5]; const sum = numbers.reduce((total, n) => total + n, 0); console.log(sum);
3. ES6 for-of
语句:
ES6标准引入了可迭代对象的概念,并定义了一种新的用于遍历数据的结构,即for...of
语句。
这个语句适用于任何类型的可迭代对象,也适用于生成器(任何具有\[Symbol.iterator\]
属性的对象)。
在ES6中,数组对象是内置的可迭代对象,因此可以对它们使用这个语句:
let colors = ['red', 'green', 'blue']; for (const color of colors){ console.log(color); }
优点
- 它可以遍历各种各样的对象。
- 可以使用正常的流程控制语句(
break
/continue
)。 - 用于迭代串行异步值非常有用。
缺点
- 如果你的目标是旧的浏览器,转换后的输出可能会让你感到意外。
不要使用for...in
@zipcodeman建议使用for...in
语句,但是对于迭代数组来说,应该避免使用for-in
语句,该语句用于枚举对象属性。
不应该用于类数组对象,因为:
- 迭代的顺序不被保证;数组索引可能不是按数值顺序访问的。
- 继承的属性也被枚举了。
第二点可能会给你带来很多问题,例如,如果你扩展了Array.prototype
对象来包含一个方法,那么该属性也会被枚举。
例如:
Array.prototype.foo = "foo!"; var array = ['a', 'b', 'c']; for (var i in array) { console.log(array[i]); }
上面的代码将会在控制台上输出"a"、"b"、"c"和"foo!"。
如果你使用一些大量依赖于本地原型扩展的库(例如MooTools)可能会产生问题。
正如我之前说过的,for-in
语句是用来枚举对象属性的,例如:
var obj = { "a": 1, "b": 2, "c": 3 }; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { // or if (Object.prototype.hasOwnProperty.call(obj,prop)) for safety... console.log("prop: " + prop + " value: " + obj[prop]) } }
在上面的示例中,hasOwnProperty
方法允许您仅枚举自身属性。也就是说,只有对象物理上具有的属性,没有继承的属性。
我建议您阅读以下文章: