第一种方式(for循环)
原始的for循环,也是最常见的一种遍历方式,示例如下所示:
let my_array = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; for (let i = 0; i < my_array.length; i++) { console.log(my_array[i]); // 打印 // 10 // 20 // 30 // 40 // 50 // 60 // 70 // 80 // 90 // 100 }
第二种方式(forEach 遍历)
可以通过数组原型上的方法(forEach)进行遍历,示例如下:
let my_array = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; my_array.forEach((res, index, e)=>console.log(res, index, e)) // 打印 // 10 0 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 20 1 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 30 2 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 40 3 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 50 4 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 60 5 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 70 6 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 80 7 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 90 8 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // 100 9 [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
语法:
forEach(callbackFn)
forEach(callbackFn, thisArg)
callbackFn
回调函数为数组中每个元素执行的函数。并会丢弃它的返回值。该函数被调用时将传入以下参数:
element
数组中正在处理的当前元素。
index
数组中正在处理的当前元素的索引。
array
调用了 forEach() 的数组本身。
thisArg 可选
执行 callbackFn 时用作 this 的值。如果是采用箭头函数的形式,此参数就显得无关紧要了。
返回值
undefined
描述:
forEach() 方法是一个迭代方法
。它按索引升序地为数组中的每个元素调用一次提供的 callbackFn 函数。与 map()
不同,forEach() 总是返回 undefined
,而且不能继续链式调用。其典型的用法是在链式调用的末尾执行某些操作。
callbackFn 仅对已赋值的数组索引调用。对于稀疏数组
中的空槽,它不会被调用。
实例如下:
let my_array = new Array(10); my_array.forEach(res=>console.log(res + '这是测试稀疏数组'));
运行代码,发现没有也没打印。
forEach() 不会改变其调用的数组,但是,作为 callbackFn 的函数可以更改数组。请注意,在第一次调用 callbackFn 之前,数组的长度已经被保存。因此:
- 当调用
forEach()
时,callbackFn
不会访问超出数组初始长度的任何元素。 - 已经访问过的索引的更改不会导致
callbackFn
再次调用它们。 - 如果
callbackFn
更改了数组中已经存在但尚未访问的元素,则传递给callbackFn
的值将是在访问该元素时的值。已经被删除的元素不会被访问。
示例如下所示:
let my_array = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; my_array.forEach((res, index, e)=>{ if(!index){ my_array.push(1000) } console.log(res) }) // 打印 // 10 // 20 // 30 // 40 // 50 // 60 // 70 // 80 // 90 // 100
发现在遍历数组时,新增的数据并没有打印出来。
除非抛出异常,否则没有办法停止或中断 forEach() 循环。如果有这样的需求,则不应该使用 forEach() 方法。
可以通过像 for、for...of 和 for...in 这样的循环语句来实现提前终止。当不需要进一步迭代时,诸如
some()、find() 和 findIndex() 等数组方法也会立即停止迭代。
forEach() 返回的是一个同步函数,它不会等待 Promise 的返回。
第三种方式(for...in)
for...in 语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。
语法
for (variable in object)
statement
variable
在每次迭代时,variable 会被赋值为不同的属性名。
object
非 Symbol 类型的可枚举属性被迭代的对象。
注意:for ... in是为遍历对象属性而构建的,不建议与数组一起使用。
代码示例如下所示:
let my_array = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; for (let index in my_array) { console.log(index, my_array[index]); } // 打印 // 0 10 // 1 20 // 2 30 // 3 40 // 4 50 // 5 60 // 6 70 // 7 80 // 8 90 // 9 100
第四种方式(for...of)
for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
语法:
for (variable of iterable) { //statements }
variable
在每次迭代中,将不同属性的值分配给变量。
iterable
被迭代枚举其属性的对象。
示例如下所示:
let my_array = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; for (let v of my_array) { console.log(v); } // 打印 // 10 // 20 // 30 // 40 // 50 // 60 // 70 // 80 // 90 // 100
对于for...of的循环,可以由 break, throw 或 return 终止。在这些情况下,迭代器会进行关闭。
示例代码如下所示:
let my_array = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; for (let v of my_array) { if(v > 60) { break; } console.log(v); } // 打印 // 10 // 20 // 30 // 40 // 50 // 60
第五种方式(map方法遍历)
map() 方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
语法
map(callbackFn)
map(callbackFn, thisArg)
参数
callbackFn
为数组中的每个元素执行的函数。它的返回值作为一个元素被添加为新数组中。该函数被调用时将传入以下参数:
element
数组中当前正在处理的元素。
index
正在处理的元素在数组中的索引。
array
调用了 map()
的数组本身。
thisArg
(可选)
执行 callbackFn
时用作 this
的值。
map() 方法是一个迭代方法。它为数组中的每个元素调用一次提供的 callbackFn 函数,并用结果构建一个新数组。
map() 方法是一个复制方法。它不会改变 this。然而,作为 callbackFn 提供的函数可以更改数组。请注意,在第一次调用 callbackFn
之前,数组的长度已经被保存。因此:
- 当开始调用
map()
时,callbackFn
将不会访问超出数组初始长度的任何元素。 - 对已访问索引的更改不会导致再次在这些元素上调用
callbackFn
。 - 如果数组中一个现有的、尚未访问的元素被
callbackFn
更改,则它传递给callbackFn
的值将是该元素被修改后的值。被删除的元素则不会被访问。
一般数组常用的遍历方式大概就是这5种。
六、性能对比
Benchmark.js 是 loash的作者(John-David Dalton)创建的一个用于基准测试的类库。首先我们引入Benchmark.js 进行基准测试。
Benchmark.js 如何使用可参考github地址:我们创建一个数组,包含10000个元素,然后通过上面5种方式遍历以下,对比以下性能,代码如下所示:
var Benchmark = require('benchmark'); var suite = new Benchmark.Suite; var array10000 = Array.from({ length: 10000 }, (o, i) => i + 1); // 增加测试 suite.add('for#Array', function() { for(let i = 0; i < array10000.length; i++){ } }) .add('forEach#Array', function() { array10000.forEach(res=>{}) }) .add('forIn#Array', function() { for (let i in array10000){ } }) .add('forOf#Array', function() { for (let i of array10000){ } }) .add('map#Array', function() { array10000.map(res=>{}) }) // 增加监听 .on('cycle', function(event) { console.log(String(event.target)); }) // 最后一个测试的完成事件 .on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); }) // 是否异步运行 .run({ 'async': true });
执行结果如下所示:
for#Array x 398,690 ops/sec ±0.58% (92 runs sampled) forEach#Array x 94,419 ops/sec ±40.29% (88 runs sampled) forIn#Array x 6,170 ops/sec ±2.58% (90 runs sampled) forOf#Array x 238,086 ops/sec ±1.47% (93 runs sampled) map#Array x 26,244 ops/sec ±17.69% (86 runs sampled) Fastest is for#Array
Ops/sec: 代表以每秒钟执行代码的次数
±17.69%: 方差,波动越小,数据越稳定。方差的稳定性也和抽样数也会有影响,一般来说,抽样样本越多,方差波动越小,数据越稳定。
88 runs sampled :运行88个抽样
从以上返回的结果来看,数组遍历的时候性能从高到低的依次是:
For > for of > forEach > map > for in
结论:
这只是一个大概的测试结果,影响测试的结果也包括运行数据的大小,运行抽样数据的大小等因素。但是这也可以基本反映出它们各自的一个性能。