阅读 470

数组遍历方式大汇总 | JavaScript篇

这是我参与更文挑战的第 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值、数组下标index以及原数组arr

需要注意的是,如果回调函数是箭头函数,那么通过第二个参数修改this执行时不能修改成功的。因为箭头函数this默认是执行外围非箭头函数执行的thiscallapply以及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指向,回调函数的参数也是valueindexarray

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指向,回调函数的参数也是valueindexarray

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

reduceRightreduce极其相似,只不过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]]
复制代码

END

文章分类
前端
文章标签