一尘不染

在 JavaScript 中循环遍历数组

javascript

在 Java 中,您可以使用for循环遍历数组中的对象,如下所示:

String[] myStringArray = {"Hello", "World"};
for (String s : myStringArray)
{
    // Do something
}

你能在 JavaScript 中做同样的事情吗?


阅读 213

收藏
2022-01-13

共2个答案

一尘不染

三个主要选项:

  1. for (var i = 0; i < xs.length; i++) { console.log(xs[i]); }
  2. xs.forEach((x, i) => console.log(x));
  3. 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
}

优点

  • 适用于各种环境
  • 您可以使用breakcontinue流控制语句

缺点

  • 太冗长
  • 至关重要的
  • 容易出现一个错误(有时也称为栅栏柱错误

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

通常,您可以break通过在迭代数组元素之前过滤数组元素来替换退出命令式循环的需要,例如:

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 });

*地图的*正确用例:

const numbers = [1,2,3,4,5];
const doubled = numbers.map(n => n * 2);

console.log(doubled);

另外,如果你想降低数组的值,例如,要总结数字数组,你应该使用减少方法。

反模式:

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. ES6for-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方法只允许您枚举自己的属性。就是这样,只有对象物理具有的属性,没有继承的属性。

2022-01-13
一尘不染

是的,假设您的实现包括forofECMAScript 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;该报告的数字仅比存储值的最高索引大一。如果数组包含的元素少于其长度所指示的元素,则称其为sparse。例如,拥有一个仅在索引 3、12 和 247 处包含项目的数组是完全合法的;在length这样的阵列的报告为248,尽管它仅实际存储3个值。如果您尝试访问任何其他索引处的项目,则该数组将显示在undefined那里具有该值。所以当你想“循环遍历”一个数组时,你有一个问题要回答:你想循环遍历它的长度和过程所指示的整个范围吗?undefineds 对于任何缺失的元素,还是只想处理实际存在的元素?这两种方法都有很多应用。这仅取决于您使用数组的目的。

如果您使用for..遍历数组of,则循环体将执行length多次,并且循环控制变量设置undefined为数组中实际不存在的任何项目。根据您“使用”代码的详细信息,该行为可能是您想要的,但如果不是,您应该使用不同的方法。

当然,一些开发人员别无选择,只能使用不同的方法,因为无论出于何种原因,他们的目标是一个尚不支持的 JavaScript 版本forof

只要您的 JavaScript 实现符合ECMAScript 规范的先前版本(例如,排除了 Internet Explorer 9 之前的版本),您就可以使用Array#forEach迭代器方法而不是循环。在这种情况下,您传递一个要在数组中的每个项目上调用的函数:

var myStringArray = [ "Hello", "World" ];
myStringArray.forEach( function(s) { 
     // ... do something with s ...
} );

for…不同of.forEach仅调用数组中实际存在的元素的函数。如果传递我们假设的具有三个元素且长度为 248 的数组,它只会调用该函数 3 次,而不是 248 次。它还区分缺失元素和实际设置为的元素undefined;对于后者,它仍然会调用函数,undefined作为参数传递。如果这是您想要处理稀疏数组.forEach的方式,即使您的解释器支持for…也可能是要走的路of

最后一个适用于所有JavaScript 版本的选项是显式计数循环。您只需从 0 数到比长度小一,然后使用计数器作为索引。基本循环如下所示:

var i, s, myStringArray = [ "Hello", "World" ], len = myStringArray.length;
for (i=0; i<len; ++i) {
  s = myStringArray[i];
  // ... do something with s ...
}

这种方法的一个优点是您可以选择如何处理稀疏数组;上面的代码将完整运行循环体length,并s设置undefined为任何缺少的元素,就像for.. of。如果您只想处理稀疏数组中实际存在的元素,例如.forEach,您可以in在索引上添加一个简单的测试:

var i, s, myStringArray = [ "Hello", "World" ], len = myStringArray.length;
for (i=0; i<len; ++i) {
  if (i in myStringArray) {
    s = myStringArray[i];
    // ... do something with s ...
  }
}

将长度值分配给局部变量(与myStringArray.length在循环条件中包含完整表达式相反)可以显着提高性能,因为它每次都会跳过属性查找;在我的机器上使用 Rhino,加速是 43%。

您可能会在循环初始化子句中看到长度缓存,如下所示:

var i, len, myStringArray = [ "Hello", "World" ];
for (len = myStringArray.length, i=0; i<len; ++i) {

如果需要,显式计数循环还意味着您可以访问每个值的索引。索引也作为额外参数传递给您传递给的函数forEach,因此您也可以通过这种方式访问它:

myStringArray.forEach( function(s, i) {
   // ... do something with s and i ...
});

forof不会为您提供与每个对象关联的索引,但只要您要迭代的对象实际上是一个Arrayfor..of适用于可能没有此方法的其他可迭代类型),您就可以使用Array #entries方法将其更改为 [index, item] 对的数组,然后对其进行迭代:

for (const [i, s] of myStringArray.entries()) {
  // ... do something with s and i ...
}

其他人提到的forin语法是用于循环对象的属性;由于 JavaScript 中的 Array 只是一个具有数字属性名称的对象(以及一个自动更新的length属性),因此理论上您可以使用它循环遍历 Array。但问题是它并不局限于数字属性值(请记住,即使方法实际上也只是其值为闭包的属性),也不能保证按数字顺序迭代那些。因此,forin语法应该用于遍历数组。

2022-01-13