本文总结js中的各种常用遍历。

基本用法

for

最基本的遍历写法,通过索引来遍历。

1
2
3
4
5
let arr = [1, 2, 3]
for(let i = 0; i < arr.length; i++){
console.log(i) // 索引,数组下标 0, 1, 2
console.log(arr[i]) // 数组下标所对应的元素 1, 2, 3
}

forEach

写法比for简便,遍历所有元素,不返回任何值。

1
2
let arr = [1, 2, 3]
arr.forEach(i => console.log(i)) // 1, 2, 3

map

映射,常用于基于原数组返回新数组(不改变原数组)。

1
2
3
4
let arr = [1, 2, 3]
const newArr = arr.map(i => i + 1)
console.log(arr) // [1, 2, 3]
console.log(newArr) // [2, 3, 4]

filter

筛选,常用于过滤原数组来产生新数组,不改变原数组。

1
2
3
4
let arr = [1, 2, 3]
const newArr = arr.filter(i => i > 1)
console.log(arr) // [1, 2, 3]
console.log(newArr) // [2, 3]

以上4种遍历基本满足大部分需求,下面看一些用的较少的遍历方法。

reduce

将数组中的元素逐个进行处理,并将它们合并为一个值。功能十分强大,回调函数可以进行各种复杂的操作,包括条件判断、对象构建等。

语法:

1
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  • callback (执行数组中每个值的函数,包含四个参数)
    • total- 必需 (初始值, 或者计算结束后的返回值)
    • currentValue - 必需 (数组中当前被处理的元素)
    • currentIndex - 可选 (当前元素在数组中的索引)
    • arr - 可选 (调用 reduce 的数组)
  • initialValue - 可选 (作为第一次调用 callback 的第一个参数,如果不提供,第一次回调会使用数组的第一个元素)

示例(最简单的累加):

1
2
3
4
5
6
7
8
9
10
11
let arr = [1, 2, 3]
const sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index)
return prev + cur
})
console.log(arr, sum)

// 输出:
// 1 2 1
// 3 3 2
// [1, 2, 3] 6

比如我们要统计字符串中每个字母出现的次数:

1
2
3
4
5
6
7
8
9
const arrString = 'abcdaabc'

arrString.split('').reduce(function(res, cur) {
res[cur] = (res[cur] || 0) + 1
return res
}, {})

// 输出:
// {a: 3, b: 2, c: 2, d: 1}

every

条件都满足返回true。

1
2
3
4
5
let arr = [1, 2, 3]
const res = arr.every((item, index) => {
return item > 1
})
console.log(res) // false

some

有一个条件满足返回true。

1
2
3
4
5
let arr = [1, 2, 3]
const res = arr.some((item, index) => {
return item > 1
})
console.log(res) // true

for…in

主要用于遍历对象的可枚举属性(还会遍历原型链上的可枚举属性)。

遍历数组(不推荐,会遍历数组的所有可枚举属性,包括非索引属性和原型链上的属性):

1
2
3
4
5
6
7
8
9
let arr = [1, 2, 3]
for(let index in arr) {
console.log(index, arr[index])
}

// 输出:
// 0 1
// 1 2
// 2 3

遍历对象:

1
2
3
4
5
6
7
8
9
let obj = { a: 1, b: 2, c: 3 }
for(let key in obj) {
console.log(key, obj[key])
}

// 输出:
// a: 1
// b: 2
// c: 3

for…of

用于遍历可迭代对象(例如 Array, Map, Set, String, TypedArray,NodeList以及其他 DOM 集合,arguments 对象等)的可迭代属性(不可迭代属性会被忽略)。

1
2
3
4
5
6
7
8
9
10
const arr = [1, 2, 3];

for (const value of arr) {
console.log(value);
}

// 输出:
// 1
// 2
// 3

for...infor...of的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.prototype.objCustom = function () {}
Array.prototype.arrCustom = function () {}

let iterable = [3, 5, 7]
iterable.foo = "hello"

for (let i in iterable) {
console.log(i) // 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i of iterable) {
console.log(i) // 3, 5, 7
}

可以看到,主要有3点不同:

  • 1.for...in遍历key,for...of遍历value
  • 2.for...in遍历的是可枚举属性,for...of遍历的是可迭代属性
  • 3.对于array的不可迭代元属性objCustomarrCustom实例属性foo,在for...of循环中都被忽略

Object.keys,values,entries

对于普通对象(需要注意和map的区别):

  • Object.keys(obj):返回一个包含该对象所有的键的数组。
  • Object.values(obj):返回一个包含该对象所有的值的数组。
  • Object.entries(obj):返回一个包含该对象所有 [key, value] 键值对的数组。
1
2
3
4
5
6
7
let user = {
name: "John",
age: 30,
}
console.log(Object.keys(user)) // ["name", "age"]
console.log(Object.values(user)) // ["John", 30]
console.log(Object.entries(user)) // [ ["name","John"], ["age",30] ]

Object.entries把 obj 变成由键/值对组成的数组,然后使用 Object.fromEntries可以将结果转回成原来的对应

1
2
3
4
5
6
7
let user = {
name: "John",
age: 30,
}

console.log(Object.entries(user)) // [ ["name","John"], ["age",30] ]
console.log(Object.fromEntries(Object.entries(user))) // { name: 'John', age: 30 }

中断循环

中断循环,推荐使用break,continue不退出循环,只跳过当前循环,if条件判断替换continue,可读性更高。
for循环(for/for...in/for...of)可通过break退出循环。

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
let arr = [1, 2, 3]
for (let i = 0; i < arr.length; i++) {
console.log(i)
if(i === 1) break
}

// 输出:
// 0
// 1


for (let value of arr) {
console.log(value)
if(value === 1) break
}

// 输出:
// 1

let obj = { a: 1, b: 2, c: 3 }
for(let key in obj) {
console.log(key, obj[key])
if (key === 'a') break
}

// 输出:
// a 1

try {
let arr = [1, 2, 3]
arr.forEach(i => {
console.log(i)
if(i === 1) {
throw new Error('End Iterative')
}
})
} catch(e) {
console.log(e)
}

// 输出:
// 11
// Error: End Iterative

总结,有for关键字时,可以通过break退出循环,forEach可以通过throw Error的方式退出循环(但不推荐这样写,推荐用for)。

异步

当遍历碰到asyncawaitPromise时,又该怎么写呢?

1
2
3
4
let arr = [20, 10, 30]
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms))
}

上面有个数组arr和耗时操作sleep,如果没有耗时操作,就是下面同步的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const syncRes = arr.map((i) => {
console.log('loop', i)
console.log(i)
return i
})

console.log('syncRes', syncRes)

// 输出:
// loop 20
// 20
// loop 10
// 10
// loop 30
// 30
// syncRes [20, 10, 30]

如果有耗时操作,像下面这样,怎么保持输出还是[20, 10, 30]呢?

1
2
await sleep(i)
return i

最简单的方法可以用for循环(for/for...in/for...of都一样):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let asyncRes1 = []
for (let i of arr){
console.log('loop', i)
await sleep(i)
console.log(i)
asyncRes1.push(i)
}
console.log('asyncRes1', asyncRes1)

// 输出:
// loop 20
// 20
// loop 10
// 10
// loop 30
// 30
// asyncRes1 [20, 10, 30]

map可以像这样Promise.all(arr.map(async (...) => ...))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const asyncRes = await Promise.all(arr.map(async (i) => {
console.log('loop', i)
await sleep(i)
console.log(i)
return i
}))

console.log('asyncRes', asyncRes)

// 输出:
// loop 20
// loop 10
// loop 30
// 10
// 20
// 30
// asyncRes [20, 10, 30]

如果不用Promise.all,就是下面的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const asyncRes = arr.map(async (i) => {
console.log('loop', i)
await sleep(i)
console.log(i)
return i
})

console.log('asyncRes', asyncRes)

// 输出:
// loop 20
// loop 10
// loop 30
// asyncRes [Promise, Promise, Promise]
// 10
// 20
// 30

碰到reduce时async (prev, cur) => await prev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const asyncRes = await arr.reduce(async (prev, cur) => {
console.log('loop', cur)
await sleep(cur)
const res = (await prev) + cur
console.log(res)
return res
}, 0)

console.log('asyncRes', asyncRes)

// 输出:
// loop 20
// loop 10
// loop 30
// 20
// 30
// 60
// asyncRes 60

forEachfilter等其他循环中需要使用异步,先用mapreducefor...of处理。

总结

  • 基本用法要熟记特性,灵活选用
  • 需要中断就用for(for/for...in/for...of),使用break退出
  • 碰到异步用for(for/for...in/for...of)/map/reduce

参考文档