js数组遍历方法及效率测试

js 中遍历数组并不会改变原始数组的方法总共有 12 个:

1
2
3
4
ES5:
forEach、every 、some、 filter、map、reduce、reduceRight、
ES6:
find、findIndex、keys、values、entries

方法

forEach

1
2
3
4
5
array.forEach(function(currentValue, index, arr), thisValue)
// 回调函数的参数
//1. currentValue(必须),数组当前元素的值
//2. index(可选), 当前元素的索引值
//3. arr(可选),数组对象本身

关于 forEach()你要知道:

  • 无法中途退出循环,只能用 return 退出本次回调,进行下一次回调。
  • 它总是返回 undefined 值,即使你 return 了一个值。
1
2
3
4
5
6
7
8
9
10
11
12
13
let a = [1, 2, , 3]; // 第三个元素是空的,不会遍历(undefined、null会遍历)
let obj = { name: "OBKoro1" };
let result = a.forEach(function(value, index, array) {
a[3] = "改变元素";
a.push("添加到尾端,不会被遍历");
console.log(value, "forEach传递的第一个参数"); // 分别打印 1 ,2 ,改变元素
console.log(this.name); // OBKoro1 打印三次 this绑定在obj对象上
// break; // break会报错
return value; // return只能结束本次回调 会执行下次回调
console.log("不会执行,因为return 会执行下一次循环回调");
}, obj);
console.log(result); // 即使return了一个值,也还是返回undefined
// 回调函数也接受接头函数写法

every 检测数组所有元素是否都符合判断条件

1
2
3
4
5
array.every(function(currentValue, index, arr), thisValue)
// 回调函数的参数
//1. currentValue(必须),数组当前元素的值
//2. index(可选), 当前元素的索引值
//3. arr(可选),数组对象本身

方法返回值规则:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true
1
2
3
4
5
6
7
8
function isBigEnough(element, index, array) {
return element >= 10; // 判断数组中的所有元素是否都大于10
}
let result = [12, 5, 8, 130, 44].every(isBigEnough); // false
let result = [12, 54, 18, 130, 44].every(isBigEnough); // true
// 接受箭头函数写法
[12, 5, 8, 130, 44].every(x => x >= 10); // false
[12, 54, 18, 130, 44].every(x => x >= 10); // true

some 数组中的是否有满足判断条件的元素

1
2
3
4
5
array.some(function(currentValue, index, arr), thisValue)
// 回调函数的参数
//1. currentValue(必须),数组当前元素的值
//2. index(可选), 当前元素的索引值
//3. arr(可选),数组对象本身

方法返回值规则:

如果有一个元素满足条件,则表达式返回 true, 剩余的元素不会再执行检测。

如果没有满足条件的元素,则返回 false。

1
2
3
4
5
function isBigEnough(element, index, array) {
return element >= 10; //数组中是否有一个元素大于 10
}
let result = [2, 5, 8, 1, 4].some(isBigEnough); // false
let result = [12, 5, 8, 1, 4].some(isBigEnough); // true

filter 过滤原始数组,返回新数组

1
2
3
4
5
let new_array = arr.filter(function(currentValue, index, arr), thisArg)
// 回调函数的参数
//1. currentValue(必须),数组当前元素的值
//2. index(可选), 当前元素的索引值
//3. arr(可选),数组对象本身
1
2
3
4
5
let a = [32, 33, 16, 40];
let result = a.filter(function(value, index, array) {
return value >= 18; // 返回a数组中所有大于18的元素
});
console.log(result, a); // [32,33,40] [32,33,16,40]

map 对数组中的每个元素进行处理,返回新的数组

1
2
3
4
5
let new_array = arr.map(function(currentValue, index, arr), thisArg)
// 回调函数的参数
//1. currentValue(必须),数组当前元素的值
//2. index(可选), 当前元素的索引值
//3. arr(可选),数组对象本身
1
2
3
4
5
6
let a = ["1", "2", "3", "4"];
let result = a.map(function(value, index, array) {
return value + "新数组的新元素";
});
console.log(result, a);
// ["1新数组的新元素","2新数组的新元素","3新数组的新元素","4新数组的新元素"] ["1","2","3","4"]

reduce 为数组提供累加器,合并为一个值

1
2
3
4
5
6
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
// 回调函数的参数
//1. total(必须),初始值, 或者上一次调用回调返回的值
//2. currentValue(必须),数组当前元素的值
//3. index(可选), 当前元素的索引值
//4. arr(可选),数组对象本身

回调第一次执行时:

如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为,那么将抛出 TypeError
如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。

1
2
3
4
5
6
7
8
// 数组求和
let sum = [0, 1, 2, 3].reduce(function(a, b) {
return a + b;
}, 0);
// 6
// 将二维数组转化为一维 将数组元素展开
let flattened = [[0, 1], [2, 3], [4, 5]].reduce((a, b) => a.concat(b), []);
// [0, 1, 2, 3, 4, 5]

reduceRight 从右至左累加

这个方法除了与 reduce 执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。

ES6:find()& findIndex() 根据条件找到数组成员

find()定义:用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回 undefined。

findIndex()定义:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

1
2
3
4
5
6
let new_array = arr.find(function(currentValue, index, arr), thisArg)
let new_array = arr.findIndex(function(currentValue, index, arr), thisArg)
// 回调函数的参数
//1. currentValue(必须),数组当前元素的值
//2. index(可选), 当前元素的索引值
//3. arr(可选),数组对象本身

这两个方法都可以识别 NaN,弥补了 indexOf 的不足.

1
2
3
4
5
6
7
8
// find
let a = [1, 4, -5, 10].find(n => n < 0); // 返回元素-5
let b = [1, 4, -5, 10, NaN].find(n => Object.is(NaN, n)); // 返回元素NaN
let c = [1, 4, -5, 10].find(n => Object.is(NaN, n)); // 返回undefined
// findIndex
let a = [1, 4, -5, 10].findIndex(n => n < 0); // 返回索引2
let b = [1, 4, -5, 10, NaN].findIndex(n => Object.is(NaN, n)); // 返回索引4
let c = [1, 4, -5, 10].findIndex(n => n > 10); // 返回索引-1

keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (let index of ["a", "b"].keys()) {
console.log(index);
}
// 0
// 1

for (let elem of ["a", "b"].values()) {
console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ["a", "b"].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"

在 for..of 中如果遍历中途要退出,可以使用 break 退出循环。

如果不使用 for…of 循环,可以手动调用遍历器对象的 next 方法,进行遍历:

效率

js 有如下两种数据需要经常遍历

  • 数组(Array)
  • 对象(Object)

同时又提供了如下 8 种方法方便我们遍历元素

  • for
  • while(或 do~while)
  • forEach
  • for in
  • map
  • every

最终我们将分析遍历效率选出最佳遍历选手.

数组循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
var array = [],
length = (array.length = 10000000); //(一千万)
// for(var i=0;i<length;i++){
// array[i] = 'louis';
// }
// console.log(array[0]);
//-------------------------for
var t1 = +new Date();
for (var i = 0; i < length; i++) {}
var t2 = +new Date();
console.log("for:" + (t2 - t1));

//-------------------------do/while
var t1 = +new Date();
var i = 0;
do {
i++;
} while (i < length);
var t2 = +new Date();
console.log("do while:" + (t2 - t1));

//-------------------------forEach
var t1 = +new Date();
array.forEach(function(item) {});
var t2 = +new Date();
console.log("forEach:" + (t2 - t1));

//-------------------------for in
var t1 = +new Date();
for (var item in array) {
}
var t2 = +new Date();
console.log("for in:" + (t2 - t1));

//------------------------- every
var t1 = +new Date();
array.every(() => {});
var t2 = +new Date();
console.log("every" + (t2 - t1));

//------------------------- some
var t1 = +new Date();
array.some(() => {});
var t2 = +new Date();
console.log("some" + (t2 - t1));

//-------------------------map
var t1 = +new Date();
array.map(function(num) {});
var t2 = +new Date();
console.log("map:" + (t2 - t1));

//-------------------------filter
var t1 = +new Date();
array.filter(function(e, i, arr) {});
var t2 = +new Date();
console.log("filter:" + (t2 - t1));
//-------------------------reduce
var t1 = +new Date();
array.reduce(function(e, i, arr) {});
var t2 = +new Date();
console.log("reduce:" + (t2 - t1));

415446946848

所以最终结果

  • 1、for 与 do while
  • 2、forEach map some every filter (这 5 个不相上下,可认为运行速度差不多)
  • 3、for in (多次运行不稳定)

对象循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var array = [],
length = (array.length = 10000000); //(一千万)
var objects = {};
for (var i = 0; i < array.length; i++) {
objects[i] = Math.random();
}

//-------------------------for in
var t1 = +new Date();
for (var attr in objects) {
}
var t2 = +new Date();
console.log("for in:" + (t2 - t1));

//-------------------------Object.keys
var t1 = +new Date();
Object.keys(objects);
var t2 = +new Date();
console.log("Object.keys" + (t2 - t1));

//-------------------------Object.values
var t1 = +new Date();
Object.values(objects);
var t2 = +new Date();
console.log("Object.values" + (t2 - t1));

//-------------------------Object.getOwnPropertyNames
var t1 = +new Date();
Object.getOwnPropertyNames(objects);
var t2 = +new Date();
console.log("Object.getOwnPropertyNames" + (t2 - t1));

546541654156

结果出乎意料

  • 1、Object.values
  • 2、Object.keys、for in
  • 3、Object.getOwnPropertyNames