详细

遍历数组的四种方法对比:for vs for-in vs forEach() vs for-of(译文)

作者:Dr. Axel Rauschmayer 原文网址:https://2ality.com/2021/01/looping-over-arrays.html

本文主要比较遍历数组的四种方法:

  • for循环
for (let index=0; index < someArray.length; index++) {
const elem = someArray[index];
// ···
}
  • for-in循环
for (const key in someArray) {
console.log(key);
}
  • .forEach()循环
someArray.forEach((elem, index) => {
console.log(elem, index);
});
  • for-of循环
for (const elem of someArray) {
console.log(elem);
}

以上四种遍历方式,for-of大部分情况下是最佳选择,我们具体分析一下原因。

[TOC]

1 for循环[ES1]

for循环出现的比较早,在ECMAScript 1就存在了。for循环可以遍历每个数组元素的索引和值:

const arr = ['a', 'b', 'c'];
arr.prop = 'property value';
for (let index=0; index < arr.length; index++) {
const elem = arr[index];
console.log(index, elem);
}
// Output:
// 0, 'a'
// 1, 'b'
// 2, 'c'

这种循环方式有什么优缺点呢?

  • 首先它非常通用,但是当我们只想循环一个数组时,这种方式显的特别冗余。
  • 这种方式允许我们可以不用从第一个元素开始循环,其它循环方式就做不到这一点。

2 for-in循环[ES1]

for-in循环和for循环一样,也是在ECMAScript 1就存在了。for-in遍历的是数组元素的索引:

const arr = ['a', 'b', 'c'];
arr.prop = 'property value';
for (const key in arr) {
console.log(key);
}
// Output:
// '0'
// '1'
// '2'
// 'prop'

最好不要用for-in来遍历数组:

  • 这种方式遍历出来的是属性键,而不是值。
  • 它遍历的属性键即数组元素的索引是字符串,不是数字(关于数组元素工作原理的更多信息
  • 它遍历所有可枚举的属性键(包括自身的和继承的),而不仅仅是那些数组元素。 for-in访问继承属性确实有一个场景: 遍历对象所有可枚举属性。但即使这样,我也更喜欢手动迭代原型链,因为可以更好的把控。

3 数组方法.forEach()[ES5]

forfor-in方法在遍历数组时不是特别适合,在ECMAScript 5里面提供了一个方法: Array.prototype.forEach()

const arr = ['a', 'b', 'c'];
arr.prop = 'property value';
arr.forEach((elem, index) => {
console.log(elem, index);
});
// Output:
// 'a', 0
// 'b', 1
// 'c', 2

这个方法可以很方便地让我们获取数组的元素和元素,而且有了箭头函数(ES6规范引入),forEach方法写起来更优雅了。 .froEach()主要的缺点是:

  • 这个方法循环体里面不能用await
  • for循环里面,我们可以用break提前结束循环,在这个方法中无法实现。

3.1 在.forEach()中结束循环的替代方法

在我们使用使用像.foreach()循环方法并提前结束循环,有一个替代方法:.some()也会遍历所有数组元素,如果它的回调返回值为true就会结束循环。

const arr = ['red', 'green', 'blue'];
arr.some((elem, index) => {
if (index >= 2) {
return true; // break from loop
}
console.log(elem);
// This callback implicitly returns `undefined`, which
// is a falsy value. Therefore, looping continues.
});
// Output:
// 'red'
// 'green'

但是,这是对.some()的滥用,上面这段代码理解起来有点绕(跟for-of和break相比的话)。

4 for-of循环[ES6]

在ECMAScript 6中新增了for-of循环:

const arr = ['a', 'b', 'c'];
arr.prop = 'property value';
for (const elem of arr) {
console.log(elem);
}
// Output:
// 'a'
// 'b'
// 'c'

for-of才是真正的用来遍历数组的:

  • 它遍历数组元素
  • 在循环体中可以使用await
    • 如果你需要迁移到到for-await-of模式,是很方便的。
  • 即使对外部作用域,我们也可以使用breakcontinue

4.1 for-of & 循环对象

使用for-of的话,还有个优势,我们不仅可以遍历数组,也可以遍历对象,下面用Map举个例子:

const myMap = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const [key, value] of myMap) {
console.log(key, value);
}
// Output:
// false, 'no'
// true, 'yes'

遍历myMap键值对[key, value]时,我们可以直接可以解构它,来访问key和value。

4.2 for-of & 数组索引

数组方法.keys()返回一个索引数组:

const arr = ['chocolate', 'vanilla', 'strawberry'];
for (const index of arr.keys()) {
console.log(index);
}
// Output:
// 0
// 1
// 2

4.3 for-of & 数组[index, value]键值对

数组方法.entries()返回一个基于[index, value]键值对的可迭代对象。我们使用for-of去解构这个方法,就可以方便地访问索引和值:

const arr = ['chocolate', 'vanilla', 'strawberry'];
for (const [index, value] of arr.entries()) {
console.log(index, value);
}
// Output:
// 0, 'chocolate'
// 1, 'vanilla'
// 2, 'strawberry'

5 结论

综上所述,for-offor,for-in,.forEach()在可用性方面更友好。 以上四种循环机制之间的性能差异几乎可以忽略。如果你正在做一些计算密集的工作,对性能有所要求,采用WebAssembly更合适。


对于更多JavaScript的内容可以去读一下作者Dr. Axel Rauschmayer的开源书籍《JavaScript for impatient programmers》!本书的亮点:

本书始终关注最新的技术,从而降低初学者学习JavaScript的难度。