遍历数组有很多方法, 最简单的便是for循环, 另外还有forEach、map、filter、some、every、reduce等 ;
每种方法面向的场景是不一样的, 先讲一下使用, 性能嘛, 最后再看
for
for循环是最基础的一种循环方式
// 一般把长度先缓存, 避免重复获取, 无需多言
const arr = [1,2,3,4,5]
const len = arr.length;
for(i = 0; i < len; i++) {
// do something
}
for
循环可以通过break
、return
等语句提前终止
for
并没有块级作用域
当遇到return
语句时 , 包含此for
循环的方法会终止
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
一般用于对数组每一项进行修改, 没有返回值
arr.forEach(_ => {
// do something
})
由于break
, continue
只能在循环中使用, 而forEach回调function
是函数
很明显不可以
通过break
, continue
等中断循环
但是可以
通过return
结束本次循环
[1,2,3,4,5].forEach(_ => {
if(_ === 3) {
return
}
console.log(_)
})
// 1 2 4 5
// 可以看到3 没有被打印, 循环仍会继续, 所以 4 5 被打印了
如果要我非要终止循环
呢, 也不是不行, 不过我暂时只能想到抛异常的方法
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
的方式去做
Set.prototype.forEach // ƒ forEach() { [native code] }
Map.prototype.forEach // ƒ forEach() { [native code] }
那么 Set 和 Map 调用 forEach 打印的 index, value都是什么呢
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
一般是对每个元素进行一些处理, 并返回新的数组
arr.map((el, index) => {
// do something with el
return el
})
Set
, Map
没有该方法
常见用法
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
const arr = [10, 30, 8, 2, 6]
let bool = arr.every(el => {
return el % 2 === 0
})
bool // true
// 每个元素除以2余数都为0吗, 很明显, 是的
some
测试是否至少有一个元素通过由提供的函数实现的测试, 最终返回布尔值, 只要有一个元素通过则终止循环并返回 true
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
可以拿到原型上的属性
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
还有, 如果给数组加上属性, 也会被迭代出来
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都属于iterable类型, 也就是可迭代对象
for of 循环不仅支持数组、大多数伪数组对象,也支持字符串遍历,此外还支持 Map 和 Set 对象遍历
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 语句遍历可迭代对象定义要迭代的数据, 拿到的是值