循环访问 JavaScript 中的数组1. 顺序循环:for2. :Array.prototype.forEach3. ES6 语句:for-of请勿使用for...in

2022-08-29 21:46:15

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

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

我可以在JavaScript中做同样的事情吗?


答案 1

三个主要选项:

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

Arrow函数也被广泛实现,除非你计划支持古老的平台(例如,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);

此外,如果您尝试将数组简化为一个值,例如,要对数字数组求和,则应使用 reduce 方法。

反模式:

const numbers = [1,2,3,4,5];
const sum = 0;
numbers.forEach(num => { sum += num });

正确使用减少

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

优点

  • 它可以迭代各种对象。
  • 可以使用正常的流控制语句 ( / )。breakcontinue
  • 用于循环访问串行异步值。

缺点

请勿使用for...in

@zipcodeman建议使用该语句,但对于应避免迭代数组,该语句旨在枚举对象属性。for...infor-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

我建议您阅读以下文章:


答案 2

是的,假设您的实现包括 for...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

(该变量在每次迭代时都是不同的,但只要不在那里修改,它仍然可以在循环体内声明。sconst

关于稀疏数组的说明:JavaScript 中的数组实际上可能存储的项目数可能与它报告的多 。报告的数字只是比存储值的最高索引大一个。如果数组包含的元素少于其长度所指示的元素,则它被称为稀疏。例如,拥有一个仅在索引 3、12 和 247 处包含项的数组是完全合法的;这样的数组报告为 248,尽管它实际上只存储了 3 个值。如果尝试访问任何其他索引处的项,则该数组将显示为具有该值。因此,当您想要“遍历”数组时,您有一个问题需要回答:对于任何缺失的元素,您是要遍历由其长度和进程 s 指示的完整范围,还是只想处理实际存在的元素?这两种方法都有很多应用;它只取决于你使用数组的目的。lengthlengthundefinedundefined

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

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

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

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

如果您的实现支持 ES6+,您当然可以使用箭头函数:

myStringArray.forEach( s => { 
     // ... do something with s ...
} );

与 ...不同,它只为数组中实际存在的元素调用函数。如果传递具有三个元素且长度为 248 的假设数组,则它只会调用该函数三次,而不是 248 次。如果这就是您想要处理稀疏数组的方式,即使您的解释器支持....forof.forEach.forEachforof

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

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

此方法的一个优点是可以选择如何处理稀疏数组。上面的代码将完整地运行循环的主体,对于任何缺少的元素,设置为 to,就像 ..;如果您只想处理稀疏数组中实际存在的元素,例如,则可以在索引上添加一个简单的测试:lengthsundefinedforof.forEachin

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

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

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

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

for...不会为您提供与每个对象关联的索引,但只要您要迭代的对象实际上是其实例(而不是其他可迭代类型之一..的工作原理),您就可以使用 Array#entries 方法将其更改为 [index, item] 对的数组,然后循环访问:ofArrayforof

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

这。。。其他人提到的语法用于循环访问对象的属性;由于 JavaScript 中的 Array 只是一个具有数字属性名称(和自动更新的属性)的对象,因此理论上可以使用它循环访问 Array。但问题是,它并不局限于数值属性值(请记住,即使方法实际上也只是其值为闭包的属性),也不保证以数字顺序循环访问这些属性。因此,...语法应用于循环通过数组。forinlengthforin