js基础:循环方法整理

遍历数组有很多方法, 最简单的便是 for 循环, 另外还有 forEach、map、filter、some、every、reduce 等 ;
每种方法面向的场景是不一样的, 先讲一下使用, 性能嘛, 最后再看

for

for 循环是最基础的一种循环方式

1
2
3
4
5
6
// 一般把长度先缓存, 避免重复获取, 无需多言
const arr = [1, 2, 3, 4, 5]
const len = arr.length
for (i = 0; i < len; i++) {
// do something
}

for循环可以通过breakreturn 等语句提前终止
for并没有块级作用域
当遇到return语句时 , 包含此for循环的方法会终止

1
2
3
4
5
6
7
8
9
10
11
12
13
const gg = () => {
const arr = [1, 2, 3, 4, 5]
const len = arr.length
for (i = 0; i < len; i++) {
console.log(arr[i])
if (i === 3) {
return
}
}
console.log('last')
}
// return 后 'last' 不打印
gg() // 1 2 3 4

字符串同样有 length 属性, 所以可以遍历字符串

forEach

一般用于对数组每一项进行修改, 没有返回值

1
2
3
arr.forEach((_) => {
// do something
})

由于break, continue只能在循环中使用, 而 forEach 回调function是函数
很明显不可以通过break, continue等中断循环
但是可以通过return结束本次循环

1
2
3
4
5
6
7
8
;[1, 2, 3, 4, 5].forEach((_) => {
if (_ === 3) {
return
}
console.log(_)
})
// 1 2 4 5
// 可以看到3 没有被打印, 循环仍会继续, 所以 4 5 被打印了

如果要我非要终止循环呢, 也不是不行, 不过我暂时只能想到抛异常的方法

1
2
3
4
5
6
7
8
9
10
11
12
try {
;[1, 2, 3, 4, 5].forEach((_) => {
if (_ === 3) {
throw new Error('end')
}
console.log(_)
})
} catch (e) {
console.log(e)
}
// 1 2
// throw new Error('end') 是一种方法

Set, Map原型中包含forEach方法, 所以可以用于遍历Set, Map, 并不需要 Array.forEach.call / apply 的方式去做

1
2
Set.prototype.forEach // ƒ forEach() { [native code] }
Map.prototype.forEach // ƒ forEach() { [native code] }

那么 Set 和 Map 调用 forEach 打印的 index, value 都是什么呢

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
new Set(['one', 'two', 'three', NaN, NaN]).forEach((value, index, set) => {
console.log(index)
console.log(value)
})

//one
//one
//two
//two
//three
//three
//NaN
//NaN

// 可以看到, Set.forEach打印的index, value都是元素本身
// 题外话, 另一个有趣现象是, 我在new Set()传入了两个NaN, 由于Set不允许重复元素, 所以最后只会存在一个NaN值
// 但是, 不是说 NaN !== NaN 吗? 我认为, Set内部做了处理, 具体如何处理爹也不大明白

//--------------------------------------------------

//再看Map
new Map([
['key1', 'value1'],
['key2', 'value2'],
]).forEach((value, index, map) => {
console.log(index)
console.log(value)
})

//key1
//value1
//key2
//value2

//可以看到, index为元素的键, value为元素的值

map

一般是对每个元素进行一些处理, 并返回新的数组

1
2
3
4
arr.map((el, index) => {
// do something with el
return el
})

Set, Map没有该方法

常见用法

1
2
3
4
5
6
7
const arr = [
{ id: '46548', name: 'faker' },
{ id: '46549', name: 'faker2' },
]
const idList = arr.map((el) => el.id)
console.log(idList)
// ['46548', '46549']

filter / find

filter 用来查找符合条件的元素集合
find 找到第一个符合条件的元素并返回, 找到后不再往下遍历

every / some

这两个方法更多地用在测试数组的个性
every 测试数组内的所有元素是否都能通过某个指定函数的测试, 最终返回布尔值, 只要有一个元素不通过则终止循环并返回 false

1
2
3
4
5
6
const arr = [10, 30, 8, 2, 6]
let bool = arr.every((el) => {
return el % 2 === 0
})
bool // true
// 每个元素除以2余数都为0吗, 很明显, 是的

some测试是否至少有一个元素通过由提供的函数实现的测试, 最终返回布尔值, 只要有一个元素通过则终止循环并返回 true

1
2
3
4
5
6
const arr = [1, 2, 7, 11, 6]
let bool = arr.some((el) => {
return el % 2 === 0
})
bool // true
// 至少有一个元素除以2余数为0吗, 很明显, 是的

for in

for in 循环可以遍历字符串、对象、数组,不能遍历 Set/Map, 并且只能拿到可枚举的(enumerable)属性
需要注意的是, in 可以拿到原型上的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let obj = {
[Symbol('one')]: 22,
one: 11,
two: 22,
}
obj.__proto__.name = 'haha'
for (let v in obj) {
console.log(v)
}
// one, two, name
// 这里用symbol作为键时, 该属性是不可枚举的, 所以for in取不到

// 把one属性改为不可枚举
Object.defineProperty(obj, 'one', {
enumerable: false,
})
// 这个时候就取不到one了
for (let v in obj) {
console.log(v)
}
// two, name

还有, 如果给数组加上属性, 也会被迭代出来

1
2
3
4
5
6
7
const arr = [1, 2, 3]
arr.name = 'haha'
// name属性也会被遍历出来
for (let v in arr) {
console.log(v)
}
// 0,1,2,name

因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素。因此当迭代访问顺序很重要的数组时,最好用整数索引去进行 for 循环

for of

遍历 Array 可以采用下标循环,遍历 Map 和 Set 就无法使用下标。
为了统一集合类型,ES6 标准引入了新的 iterable 类型,Array、Map 和 Set 都部署了 Symbol.iterable 迭代器
for of 循环不仅支持数组、大多数伪数组对象,也支持字符串遍历,此外还支持 Map 和 Set 对象遍历

1
2
3
4
5
6
7
8
9
10
11
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);
for (let [key, value] of iterable) {
console.log(key);
console.log(value);
}
// a
// 1
// b
// 2
...
...

区别
for…in 语句以原始插入顺序迭代对象的可枚举属性, 拿到的是键
for…of 语句遍历可迭代对象定义要迭代的数据, 拿到的是值