前端对数组操作最为频繁,选择合适的数组循环遍历,可以提升开发效率。我们就通过这篇文章给大家总结一下数组常用的循环遍历方法,让大家一次性掌握这些方法的使用技巧。
注意:这种微小的性能差异通常在大多数应用中并不明显,除非在处理非常大的数据集时才可能显现出来。在实际开发中,应该优先考虑代码的可读性和维护性,而不是过度关注微小的性能差异。
for > forEach
生成一个非常大的数组,循环遍历计算数组内的和,通过startTime和endTime计算出for循环的求和消耗时间
let arrs = [...Array(9999999).keys()]
let total = 0;
let startTime = Date.now();
for(let i=0; i<arrs.length; i++) total+=i;
let endTime = Date.now();
let countTime = endTime - startTime;
console.log("计数---->"+total);
console.log("消耗时间---->"+countTime);
//计数---->49999985000001
//消耗时间---->105
数组比较大,消耗的时间还是比较长的,假设循环遍历的时候,只要满足某个条件就跳出整个循环,那么就可以使用break进行跳出,这样能够节省很大的消耗,只需要再for循环内部加个条件即可,如下所示:
let arrs = [...Array(9999999).keys()]
let total = 0;
let startTime = Date.now();
for(let i=0; i<arrs.length; i++){
if(i==10) break;
total+=i;
}
let endTime = Date.now();
let countTime = endTime - startTime;
console.log("计数---->"+total);
console.log("消耗时间---->"+countTime);
计数---->45
消耗时间---->0
同样的代码,可以看出差异来,有break跳出循环太重要了,只要数组内满足了条件后续的循环遍历就是浪费,所以说可以通过for循环的break跳出循环,大大降低循环无用的消耗。
如果使用forEach循环,将无法使用break,不但不能跳出,只要写了break语法上就报错了;
但是可以在forEach内部可以使用return,满足条件使用return可以结束当前这次循环,但是剩下的循环还是继续跑完。
let arrs = [...Array(9999999).keys()]
let total = 0;
let startTime = Date.now();
arrs.forEach(item=>{
return;
})
let endTime = Date.now();
let countTime = endTime - startTime;
console.log("计数---->"+total);
console.log("消耗时间---->"+countTime);
// 计数---->0
// 消耗时间---->91
在上面代码中,forEach内加不加return其实消耗的时间都是差不多相同的,return更像是for循环中的continue,无论如何都会遍历完全部之后才会执行forEach后面的代码。
但是在forEach中使用return可以节省计算时间,如下所示:
let arrs = [...Array(9999999).keys()]
let total = 0;
let startTime = Date.now();
arrs.forEach(item=>{
total+=item;
})
let endTime = Date.now();
let countTime = endTime - startTime;
console.log("计数---->"+total);
console.log("消耗时间---->"+countTime);
// 计数---->49999985000001
// 消耗时间---->363
下面代码是在forEach内加上条件并return结束的代码:
let arrs = [...Array(9999999).keys()]
let total = 0;
let startTime = Date.now();
arrs.forEach(item=>{
if(item>10)return;
total+=item;
})
let endTime = Date.now();
let countTime = endTime - startTime;
console.log("计数---->"+total);
console.log("消耗时间---->"+countTime);
// 计数---->55
// 消耗时间---->103
如果if(item==10) return;类似于continue只会结束当次循环,后面的11、12…会继续++,所以消耗的计数会继续累加,循环次数继续全部完成,这样写不会节省时间;
如果if(item>10) return; 可以让10后面所有的计算全部退出,那么就不会再进行计数了,最终的结果肯定是10之前的计数,return后面所有的累计不计算了,所以节省了整个的计算时间。
在一些场景下,后端给前端提供的接口不提供批量查询,那么前端就需要将要查询的ID放置到数组中,通过循环遍历查询接口,前端最后组装并展现。
以jsonplaceholder接口为例,给大家分别用for循环和forEach循环来完成上面的操作,注意:这里的操作是为了做验证用的,如果真正要完成以上操作的话建议使用map+Promise.all来完成,在后面的map讲解中会给大家介绍到。
let arrs = Array.from({length:3},(_,index)=>index+1);
let datas=[];
arrs.forEach(async item=>{
const {data} = await uni.request({
url:"http://jsonplaceholder.typicode.com/posts/"+item
})
datas.push(data)
})
console.log(datas);
// []
在上面代码中uni.request返回promise对象,使用async/await是可以实现异步等待的,按照预想应该是forEach后面打印datas是一个数组对象,包含三次接口返回的数组,但是最好打印结果是空数组,说明在forEach函数内,不支持await异步等待。
可以将上面forEach代码改造成for循环:
async function fetchData() {
let arrs = Array.from({length:3},(_,index)=>index+1);
let datas=[];
for (let i = 0; i < arrs.length; i++) {
const {data} = await uni.request({
url:"http://jsonplaceholder.typicode.com/posts/"+arrs[i]
});
datas.push(data);
}
console.log(datas);
}
fetchData();
// [{...},{...},{...}]
改造成for循环之后,是可以按照预期拿到数组集合的,但是这种查询效率会非常低,我们这里是为了证明for循环是可以做异步等待的,要是真实的场景,建议看下面map+Promise.all的实现方法。
map() 方法是数组原型的一个函数,对数组遍历不破坏原数组,将会创建一个新数组,按照原始数组元素顺序依次执行给定的函数,map方法非常适合用于处理数组中的每个元素并生成新的数组。
callbackFn
thisArg
let arrs = [1,2,3,4];
let newArrs = arrs.map(item=>item*2);
console.log(newArrs);
// [2, 4, 6, 8]
let arrs = [{name:"华为",price:6999},{name:"苹果",price:9888},{name:"小米",price:4999}]
let newArrs = arrs.map(item=>item.name);
console.log(newArrs);
// ['华为', '苹果', '小米']
let arrs = [{name:"华为",price:6999},{name:"苹果",price:9888},{name:"小米",price:4999}]
let newArrs = arrs.map(item=>{
return {
...item,
price:item.price+"元",
number:888
}
});
console.log(newArrs);
//[{name: '华为', price: '6999元', number: 888},....]
返回的是每一个对象,要是想简化成一行,必须要将对象用括号包裹起来,如下所示:
let arrs = [{name:"华为",price:6999},{name:"苹果",price:9888},{name:"小米",price:4999}]
let newArrs = arrs.map(item=>({...item,price:item.price+"元",number:888}));
console.log(newArrs);
let arrs = [{key:0,content:"篮球"},{key:1,content:"足球"},{key:2,content:"排球"}];
let newArrs = arrs.map(({key,content})=>({value:key,text:content}));
console.log(newArrs);
// [{value: 0, text: '篮球'},{value: 1, text: '足球'},{value: 2, text: '排球'}]
下面这个代码块,是解决for循环异步同步化性能消耗过大的问题。
let arrs = Array.from({length:3},(_,index)=>index+1);
const promises = arrs.map(async (num) => {
const result = await uni.request({
url:"http://jsonplaceholder.typicode.com/posts/"+num
})
return result;
});
Promise.all(promises).then(res=>{
console.log(res);
})
下面是Promise.all请求,network请求的状况,会同时发送请求,会节省很多的时间
下面是for循环异步同步化出现的网络请求状态,会等待一个完成之后再请求另一个,所以时间上会更慢一些
filter()过滤方法,会对原数组中的每个元素应用指定的函数,并返回一个新数组,其中包含符合条件的元素。原数组不会受到影响。
callbackFn
thisArg
在filter回调函数中,满足true即可被处理到新函数中,false不做处理。
let arrs = [5,7,8,15,22,1,2];
let newArrs = arrs.filter(item=>{
return item>10
})
console.log(newArrs);
// [15,22]
如果是数组对象的话也是一样的,只需要找到满足的对象属性即可,比如找到数组对象中,年龄小于30岁的数据:
let arrs = [{name:"张三",age:16},{name:"李四",age:40},{name:"王五",age:28},{name:"汤姆",age:20}];
let newArrs = arrs.filter(item=>{
return item.age<30
})
console.log(newArrs);
//[{name:"张三",age:16},...]
let arrs = [6,1,2,3,5,3,6];
let newArrs = arrs.filter((item,index,self)=>{
return self.indexOf(item) === index
})
console.log(newArrs);
// [6, 1, 2, 3, 5]
let arrs = [{id:1,name:"HTML5"},{id:2,name:"JavaScript"},{id:null,name:"小程序"},{name:"NodeJS"},{id:3,name:"VueJS"}];
let newArrs = arrs.filter(item=>{
return item.id
})
console.log(newArrs);
// [ {id: 1, name: 'HTML5'},...]
let arrs = [{id:1,name:"HTML5"},{id:2,name:"JavaScript"},{id:null,name:"小程序"},{name:"NodeJS"},{id:3,name:"VueJS"}];
let newArrs = arrs.filter(item=>{
return item.id
}).map(item=>{
return {
...item,
author:"咸虾米"
}
})
console.log(newArrs);
// [{id: 1, name: 'HTML5', author: '咸虾米'},...]
reduce() 方法对数组中的每个元素按序执行一个指定方法,每一次运行 reducer 会将先前元素的计算结果作为参数传入。
callbackFn
initialValue
let arrs = [1,2,3,4];
let result = arrs.reduce((prev,current,index)=>{
console.log(prev,current,index);
return prev+current;
})
console.log("最终结果:"+result);
从上面结果可以看到,数组内有4位成员,但是函数执行了3次,这是因为没有初始值,所以prev是数组的第一位,current是数组的第二位,索引值也是从1开始的,假设指定了初始值,效果如下:
let arrs = [1,2,3,4];
let result = arrs.reduce((prev,current,index)=>{
console.log(prev,current,index);
return prev+current;
},0)
console.log("最终结果:"+result);
求数组中最大值:
let arrs = [5,6,1,22,3,7];
let result = arrs.reduce((prev,current,index)=>{
return Math.max(prev,current);
},0)
console.log(result);
// 22
求数组中最小值:
let arrs = [5,6,1,22,3,7];
let result = arrs.reduce((prev,current,index)=>{
return Math.min(prev,current);
},0)
console.log(result);
// 0
从以上代码可以看到,初始值也是在运算范围之内
数组对象求和,求数组对象中age的和:
let arrs = [{name:"张三",age:29},{name:"李四",age:16},{name:"王五",age:50},{name:"小明",age:21}];
let result = arrs.reduce((prev,current,index)=>{
return prev+current.age
},0)
console.log(result);
// 116
判断数组中所有元素是否满足函数中给定的条件,全部满足返回true,只要有一项不满足则返回false。
callbackFn
thisArg
let arrs = [1, 2, 3, 4, 5];
let result = arrs.every(num => num > 0);
console.log(result);
//true
let arrs = [{name:"华为",price:5899,stock:true},{name:"苹果",price:9999,stock:false},{name:"小米",price:4399,stock:true},{name:"红米",price:899,stock:true}];
let result = arrs.every(item=>item.stock);
console.log(result);
// false
如下图所示,表单中的提交按钮默认是禁用的,只有当表单中所有的输入框都输入值的情况下,按钮才会由禁用转为可用状态。
<view class="layout">
<input type="text" v-model="obj.name" placeholder="请输入姓名">
<input type="text" v-model="obj.age" placeholder="请输入年龄">
<input type="text" v-model="obj.gender" placeholder="请输入性别">
<input type="text" v-model="obj.like" placeholder="请输入爱好">
<button type="primary" :disabled="state" @click="onSubmit">提交</button>
</view>
.layout{
padding:30rpx;
input{
border:1px solid #e4e4e4;
height: 80rpx;
margin-bottom:20rpx;
padding:0 20rpx;
}
}
<script setup>
import {computed, ref} from "vue";
const obj = ref({
name:"",
age:"",
gender:"",
like:""
})
const state = computed(()=>{
if(obj.value.name && obj.value.age && obj.value.gender && obj.value.like){
return false;
}
return true;
})
function onSubmit(){
console.log("提交表单");
}
</script>
简化计算属性也可以这样写:
const state = computed(() => !(obj.value.name && obj.value.age && obj.value.gender && obj.value.like));
如果使用Object.values()方法,可以将所有的值放入到一个数组中,然后对数组进行every循环遍历,最后得到条件的true或false,计算属性就可以这样做了:
const state = computed(()=>{
return !Object.values(obj.value).every(item=>item)
})
简化一下:
const state = computed(()=>!Object.values(obj.value).every(item=>item))
some方法和every方法基本类似,只是some方法检测数组中,只要有一个满足条件即返回true,全部不满足条件才会返回false。
callbackFn
thisArg
let arrs = [55,26,3,12,39];
let result = arrs.some(item=>item<10);
console.log(result);
// true
let arrs = [{name:"华为",price:5899,stock:true},{name:"苹果",price:9999,stock:false},{name:"小米",price:4399,stock:true},{name:"红米",price:899,stock:true}];
let result = arrs.some(item=>item.price<1000 && item.stock);
console.log(result);
// true
const state = computed(()=>Object.values(obj.value).some(item=>!item))
const state = computed(()=>!Object.values(obj.value).some(item=>item))
待更新…