JavaScript各遍历方法对比
# 各遍历方法简要对比
支持的数据类型 | break、continue | return | throw | async、await | 特点 | |
---|---|---|---|---|---|---|
forI | Array | 支持 | 支持 | 支持 | 支持 | 最基础的循环,语法繁琐。三个条件,均可省略。 |
forIn | Array,Object | 支持 | 支持 | 支持 | 支持 | 主要用于对象的遍历,获取其key键,但获取的key键均为String类型。不建议对数组使用,特定情况下循环会出错。 |
forOf | Array,Set,Map,String,引用类型等(更多类型可以通过iterator定义) | 支持 | 支持 | 支持 | 支持 | for ... of 修复了for ... in 对数组操作的若干问题。其主要遍历对象的值,但在对数组遍历时可以借助ES6数组提供的方法实现key、value的遍历。for ... of 可以通过实现Iterator接口对任意数据类型进行遍历。 |
forEach | Array,Set,Map,引用类型 | 不支持 | 可以使用,仅能跳出当前循环,与continue效果相似。 | 支持 | 不支持 | 在循环中break、continue俩个关键字都无效,但是可以通过return结束当轮循环,通过throw退出循环体。forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。在使用promise 或 async 作为callback函数时会出现执行顺序的问题。 |
# for ... in
该方法常用于取对象的key键,但在对数组使用时会暴露出几个缺点。
# 对数组操作的若干问题
数组的key键都是Number,但for ... in 取出的均为String
const arr = [ 5, 4, 3, 2, 1 ];
for (const i in arr) {
console.log(i); // '0', '1', '2', '3', '4'
}
2
3
4
5
循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。 导致遍历的次数与arr.length得到的长度两者不符。
const arr = [ 5, 4, 3, 2, 1 ];
arr.name = '男';
for (const i in arr) {
console.log(i); // '0', '1', '2', '3', '4', 'name'
} // 遍历6次
console.log(arr.length); // 5
2
3
4
5
6
7
# for ... of
该方法可以遍历大多常用数据类型,其中Object迭代时需要进行处理,一些复杂类型也可以通过自定义钩子进行定制迭代方式。同时其在for ... in 的基础上完善了对数组的操作。
# 修复for ... in 操作数组的缺点
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
2
3
4
5
6
7
8
9
10
# 遍历中可修改原数组的值
定义循环变量时用const不可修改原对象,通过let定义即可。
let iterable = [10, 20, 30];
for (const value of iterable) {
console.log(value); // 10 ,20 ,30
}
for (let value of iterable) {
value += 1;
console.log(value); // 11 ,21 ,31
}
2
3
4
5
6
7
8
9
10
# 在遍历数组时获取数组的key键和value值
可以借助ES6数组示例的entries(),keys()和values()方法,实现遍历数组的key和value。
const iterable = [ 10, 20, 30, 'A', 'B', 'C' ];
for (const index of iterable.keys()) {
console.log(index); // 0, 1, 2, 3, 4, 5
}
for (const elem of iterable.values()) {
console.log(elem); // 10, 20, 30, 'A', 'B', 'C'
}
for (const [ index, elem ] of iterable.entries()) {
console.log(index, elem);// 0 10, 1 20, 2 30, 3 'A', 4 'B', 5 'C'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Object对象的遍历和Iterator的实现
该方法默认无法遍历Object对象,因为其不确定遍历属性应该采用哪种顺序,故而需要进行自定义。
这块笔者理解不透彻,感兴趣的小伙伴可以移步该连接:Iterator和for ... of (opens new window)
# forEach
该方法适用于Array、Set、Map、引用类型的遍历,但不支持break,continue和return方法,以及该改变数组值等操作。
# break、continue、returen、throw 使用示例
在循环中无法通过break,continue结束当前循环,但是可以通过return结束当轮循环,通过throw退出循环体。
const arr = [ 1, 2, 3, 4, 5 ];
arr.forEach(function(item) {
if (item === 3) {
break; // Illegal break statement
}
console.log(item);
});
arr.forEach(function(item) {
if (item === 3) {
continue;; // Illegal continue statement: no surrounding iteration statement
}
console.log(item);
});
arr.forEach(function(item) {
if (item === 3) {
return;
}
console.log(item); // 1 , 2 , 4 , 5
});
try {
arr.forEach(function(item) {
if (item === 3) {
throw new Error('EndIterative');
}
console.log(item); // 1 , 2
});
} catch (error) {
if (error.message !== 'EndIterative') throw error;
}
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
# 修改迭代对象的方法
forEach被调用时,不会改变原数组,也就是调用它的数组(尽管 callback 函数在被调用时可能会改变原数组)。即,forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。
实际调用过程中发现,forEach迭代是对原对象进行复制,在其副本上进行操作。 如果是基本数据类型在callback不能对其进行改变,如果是引用类型,只能改变部分属性。 但不论那种数据类型,都可以通过forEach的第三个参数对原对象进行修改。
对基本类型的数组进行操作,无法改变其值。
const arr = [ 1, 2, 3, 4, 5 ];
arr.forEach(ele => {
ele = ele * 3;
});
console.log(arr); // [ 1, 2, 3, 4, 5 ]
2
3
4
5
6
对引用类型进行操作,可以改变其部分属性。
const objArr = [{
name: 'wxw',
age: 22,
}, {
name: 'wxw2',
age: 33,
}];
objArr.forEach(ele => {
if (ele.name === 'wxw2') {
ele.age = 88;
}
});
console.log(objArr); // [ { name: 'wxw', age: 22 }, { name: 'wxw2', age: 88 } ]
2
3
4
5
6
7
8
9
10
11
12
13
14
对引用类型进行操作,无法改变整个当前item。
const changeItemArr = [{
name: 'wxw',
age: 22,
}, {
name: 'wxw2',
age: 33,
}];
changeItemArr.forEach(ele => {
if (ele.name === 'wxw2') {
ele = {
name: 'change',
age: 77,
};
}
});
console.log(changeItemArr); //[ { name: 'wxw', age: 22 }, { name: 'wxw2', age: 33 } ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
采用第三个指针参数,可以进行任意改变。
// 基本类型
const numArr = [ 33, 4, 55 ];
numArr.forEach((ele, index, arr) => {
if (ele === 33) {
arr[index] = 999;
}
});
console.log(numArr); // [999, 4, 55]
// 引用类型
const allChangeArr = [{
name: 'wxw',
age: 22,
}, {
name: 'wxw2',
age: 33,
}];
allChangeArr.forEach((ele, index, arr) => {
if (ele.name === 'wxw2') {
arr[index] = {
name: 'change',
age: 77,
};
}
});
console.log(allChangeArr); // [{name: "wxw", age: 22},{name: "change", age: 77}]
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
# promise、async、await 使用示例
forEach对promise不敏感,其会同时出发所有的promise对象,这样速度会快,但会造成并发。例如:查询数据库中百万条数据,同时触发百万个调用。
当采用promise、async、await作为forEach的callback函数时会出现执行顺序错误的问题。
const ratings = [ 5, 4, 5 ];
let sum = 0;
const sumFunction = async function(a, b) {
return a + b;
};
ratings.forEach(async function(rating) {
sum = await sumFunction(sum, rating);
});
console.log(sum);
// Expected output: 14
// Actual output: 0
2
3
4
5
6
7
8
9
10
11
12
13
14
# 总结
过以上对比发现,`for ... of` 没有`for i`的繁琐语句,也不像`forEach`那样不支持异步和中断操作,其还可以对各种数据类型自定义迭代方式,功能强大,适用场景也多。通过结合数组的`entries()`、`keys()`、`values()`等方法,还可实现key键和value值的同时遍历,在修复`for ... in`若干Bug的基础上完美将其取代。
最后推荐大家遍历时首选for ... of
,根据情况需要选择for i
。多数情况下forEach
和for...in
可以被替代。