这是我参与更文挑战的第 6 天,活动详情查看: 更文挑战
前言
数组是开发中最经常使用的数据结构之一,里头提供了大量的属性和方法方便我们进行操作。本文将梳理JS
数组身上的所有遍历方式,如文中有误或有遗漏,希望各位批评指出,感谢!
for循环
let arr = [1, 2, 3]
for(let i = 0; i < arr.length; i++){
console.log(arr[i])
}
复制代码
以上为例来讲下普通for
循环的执行过程:
① 执行let i = 0
② 判断i
是否小于arr.length
;满足条件返回true
,不满足条件返回false
,如果为true
,循环结束,如果为false
,执行循环体中语句
③ 执行循环体语句后,执行i++
④ 重复以上②③两个步骤,直到满足条件循环结束
普通for
循环可以通过变量i
来遍历获取数组中对应下标的数组成员
while循环
while
循环是一直重复判断,当()
中的表达式结果转成boolean
值,当值为真时,执行{}
里面的代码内容,当转成boolean
值值为假时,while
循环结束
let i = 0;
while (i < arr.length) {
console.log(arr[i]);
i++;
}
复制代码
for in循环
for in
循环大多数情况是用于遍历对象,但是也能遍历数组,由于比较常用,所以也纳入进来
for (let i in arr) {
console.log(arr[i]);
}
复制代码
值得注意的是,for in
枚举数组是key
,后面会讲for of
可以直接枚举到数组的value
forEach循环
arr.forEach(function(val, index, arr){
console.log(val, index, arr);
});
复制代码
forEach
其实就是用于代替普通for
循环的,但是用起来比for
循环更方便。可以接收两个参数:第一个参数表示每次循环执行的回调函数;第二个参数表示this
指向问题。其中回调函数接受三个参数,分别表示与循环圈数对应的value
值、数组下标inde
x以及原数组arr
需要注意的是,如果回调函数是箭头函数,那么通过第二个参数修改this执行时不能修改成功的。因为箭头函数this
默认是执行外围非箭头函数执行的this
,call
和apply
以及bind
是不能修改的。
let arr = [1,2,3,4];
const obj = {name:"alice"}
arr.forEach((val,index,array)=>{
console.log(this,val,index,array);
//箭头函数不能通过第二个参数来修改其里头的this指向
},obj);
复制代码
了解了基本使用,下面来模拟实现_forEach
:
Array.prototype._forEach = function (fn, thisTo) {
for (let i = 0; i < this.length; i++) {
fn.call(thisTo,this[i], i, this);
}
}
//test code
let arr = [1,2,3,4];
const obj = {name:"alice"}
arr._forEach(function(val,index,array){
console.log(this,val,index,array);//正常函数可以通过call来修改this指向
},obj);
arr._forEach((val,index,array)=>{
console.log(this,val,index,array);//箭头函数不能通过第二个参数来修改其里头的this指向
},obj);
复制代码
实现成功
下面再来看一个跟forEach
很像的:map
循环
map循环
map
接受的参数跟forEach
一模一样,第一个参数是回调函数,第二个是this
指向,而且回调函数中的参数也是一样。
正常情况下,map
需要配合return
,返回是一个新的数组;若是没有return
,用法相当于forEach
所以map
常常用于重新整理数组里头的数据结构,如下伪代码:
let arr = [
{title:'aaaaa', read:100, hot:true},
{title:'bbbb', read:100, hot:true},
{title:'cccc', read:100, hot:true},
{title:'dddd', read:100, hot:true}
];
let newArr = arr.map((item, index, arr)=>{
let json={}
json.t = `^_^${item.title}-----`;
json.r = item.read+200;
json.hot = item.hot == true && '真棒!!!';
return json;
});
console.log(newArr);
复制代码
下面再来模拟封装一下_map
Array.prototype._map = function(fn,thisTo){
let res = []
for(let i = 0;i<this.length;i++){
res[i] = fn.call(thisTo,this[i],i,this)
}
return res;
}
//test code
let arr = [
{title:'aaaaa', read:100, hot:true},
{title:'bbbb', read:100, hot:true},
{title:'cccc', read:100, hot:true},
{title:'dddd', read:100, hot:true}
];
let newArr = arr._map((item, index, arr)=>{
let json={}
json.t = `^_^${item.title}-----`;
json.r = item.read+200;
json.hot = item.hot == true && '真棒!!!';
return json;
});
console.log(newArr);
复制代码
实现成功
filter
filter
传参跟forEach
也是一致,第一个参数是回调函数,第二个是this
指向,回调函数的参数也是value
、index
、array
。
filter
是用于过滤一些不合格“元素”, 如果回调函数返回true
,该元素就留下来
let arr = [1,2,3,4,5,6,7,8,9,10];
let res = arr.filter(function(val,index,array){
return val%2 == 0;
})
console.log(res)//[2,4,6,8,10]
复制代码
下面来手动实现_filter
:
Array.prototype._filter = function (fn, thisTo) {
let newArr = [];
let key = 0;
for (let i = 0; i < this.length; i++) {
if (fn.call(thisTo, this[i], i, this)) {
newArr[key] = this[i];
key++
}
}
return newArr;
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let res = arr._filter(function (val, index, array) {
return val % 2 == 0;
})
console.log(res)
复制代码
实现成功
some
some
传参跟forEach
也是一致,第一个参数是回调函数,第二个是this
指向,回调函数的参数也是value
、index
、array
。
some
用于表示查找,只要数组里面某个元素符合条件,结果就返回true
,否则false
let arr = [1,2,3,4,"a"];
let res = arr.some(function(val,index,array){
return val === "a";
});
console.log(res);//true
复制代码
手动封装也比较简单
Array.prototype._some = function (fn, thisTo) {
let res = false;
for (let i = 0; i < this.length; i++) {
if (fn.call(thisTo, this[i], i, this)) {
res = true;
break;
}
}
return res;
}
//test code
let arr = [1, 2, 3, 4, "b"];
let res = arr._some(function (val, index, array) {
return val === "a";
});
console.log(res); //false
复制代码
实现成功
every
跟some
类似,但不同的是some
只需要某一个元素满足条件就返回true
,而every
是需要数组中所有元素都满足条件才返回true
,如下:
let ints = [1, 2, 3, 4];
let res = ints.every((val, index, array) => {
return typeof val === 'number';
});
console.log(res);//true
复制代码
模拟封装_every
Array.prototype._every = function (fn, thisTo) {
let res = true;
for (let i = 0; i < this.length; i++) {
if (!fn.call(thisTo, this[i], i, this)) {
res = false;
break;
}
}
return res;
}
let ints = [1, 1, 1, 1];
let res = ints._every((val, index, array) => {
return val == 1;
});
console.log(res) //true
复制代码
实现!
小结一下:以上五个方法forEach/map/filter/some/every
传参都是一样,可以接收两个参数,分别是循环回调函数和this
指向,其中回调函数的参数分别对应数组成员的value
值、下标index
和原数组array
reduce
reduce
回调函数中接受四个参数:
第一个参数:total
表示初始值, 或者上次累积计算结束后的返回值
第二个参数:当前值value
第三个参数:下标index
第四个参数:原数组
我们可以使用reduce
计算数组的和或者阶层,如
let arr = [1,2,3,4,5,6,7,8,9,10];
let res = arr.reduce((total, cur, index, arr) =>{
return total+cur;
});
console.log(res);//55
复制代码
为了看清楚,我们干脆直接打印出来四个参数来看看
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.reduce((total, cur, index, arr) => {
console.log(total, cur, index, arr);
return total + cur;
});
复制代码
可以看出来,每一次的tatal
其实就是上一次的循环执行的返回结果
下面来模拟实现_reduce
Array.prototype._reduce = function (fn, thisTo) {
let total = this[0];
for (let i = 1; i < this.length; i++) {
total = fn.call(thisTo, total, this[i], i, this)
}
return total;
}
let arr = [1, 2, 3, 4, 5];
let res = arr._reduce((total, cur, index, arr) => {
console.log(total, cur, index, arr); //每一次的tatal就是上一次的循环执行的返回结果
return total + cur;
});
console.log(res)
复制代码
成功实现
reduceRight
reduceRight
与reduce
极其相似,只不过reduce
中的回调累积total
的时候,是从左往右,而reduceRight
是从右往左。例如:
let arr = [1, 2, 3, 4, 5];
let res = arr.reduceRight((total, cur, index, arr) => {
console.log(total, cur, index, arr);//每一次的tatal就是上一次的循环执行的返回结果
return total + cur;
});
console.log(res)
复制代码
手动实现_reduceRight
Array.prototype._reduceRight = function (fn, thisTo) {
let total = this[this.length - 1];
for (let i = this.length - 1 - 1; i >= 0; i--) {
total = fn.call(thisTo, total, this[i], i, this)
}
return total;
}
let arr = [1, 2, 3, 4, 5];
let res = arr._reduceRight((total, cur, index, arr) => {
console.log(total, cur, index, arr); //每一次的tatal就是上一次的循环执行的返回结果
return total + cur;
});
console.log(res)
复制代码
成功实现
下面再来看一个比较特别的for...of
循环
for of循环
for...of
适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合,这里只以数组为例
let arr = [1, 2, 3, 4, 5];
for (let val of arr) {
console.log(val);//1,2,3,4,5
}
复制代码
注意:默认遍历的就是value
值
find()
find
接受一个函数作为参数,依次拿出数组元素传入该函数,找到第一个符合条件的数组成员,如果没有找到,返回undefined
,如:
let arr = [1,2,3,1,2,3]
let res = arr.find(x=>{
return x == 2
})
console.log(res)//2
复制代码
findIndex()
与find
相似,只不过findIndex
找的是位置(索引值), 没找到返回-1
let arr = [1,2,3,1,2,3]
let res = arr.findIndex(x=>{
return x == 2
})
console.log(res)//1
复制代码
includes()
用于判断数组中是否包含某元素,包含返回true
,不包含返回false
,与字符串的 includes
方法类似;
includes
可以接受第二个参数表示搜索的起始位置,如果第二个参数为负数,则表示从倒数第几位向后搜索
let arr = [1,2,3,1,2,3]
let res = arr.includes(1)
console.log(res)//true
复制代码
keys()、values()、entries()
三者基本差不多,所以就放一起了。都返回一个迭代器对象,唯一的区别是 keys
是对键名的遍历、 values
是对键值的遍历, entries
是对键值对的遍历。通常可配合for of
使用,如下:
for (let index of ['a', 'b', 'c'].keys()) {
console.log(index);//0 1 2
}
for (let elem of ['a', 'b', 'c'].values()) {
console.log(elem);//'a', 'b', 'c'
}
for (let [index, elem] of ['a', 'b', 'c'].entries()) {
console.log(index, elem);// 0 "a" ; 1 "b"; 2 "c"
}
复制代码
flat(num/Infinity)
flat
用于“拍平”多维数组。该方法返回一个新数组,对原数据没有影响。参数表示嵌套层数,例如:
let arr = [1,2,3,[4,5,[6,7]]]
let res = arr.flat(Infinity)
console.log(res)//[1,2,3,4,5,6,7]
复制代码
flatMap()
flatMap
接受一个函数作为参数,返回一个新的数组,其中每个元素都是回调函数的结果,并且结构深度 depth
值为1
。例如:
var arr1 = [1, 2, 3, 4];
console.log(arr1.flatMap(x => [x * 2]));
// [2, 4, 6, 8]
console.log(arr1.flatMap(x => [[x * 2]]));
// [[2], [4], [6], [8]]
复制代码