javascript 遍历函数的进阶用法、组合用法

前言

上一个文章 《js 数组、对象常见的遍历用法大全》 是为大家介绍了js的常用遍历用法;
本章的话是为大家讲解一下,遍历中的进阶使用和组合使用。结合了本人实战项目经验中所遇到的,为大家详细讲解一下关于通过遍历来数据处理。关于本章里面提到的一些函数,大家可以去看我上个文章,在这边就不另做解释了。

数组去重

关于数组去重,日常开发应该也是挺常见的一种数据处理场景,
例如:
1、增加用户权限时,需要对现有权限的去重
2、对项目增加标签时,需要对现有的标签的去重
3、类似以上等等这类型的场景,经常会遇到

1、对象型数组去重

假设有数据如下:

const data1 = [
  { id: 1, name: '数据1' },
  { id: 2, name: '数据2' },
  { id: 3, name: '数据3' }
];
const data2= [
  { id: 1, name: '数据1' },
  { id: 2, name: '数据2' }
];
思路1:先合并数组,再去重
const data = [...data1,...data2];
用法1: Array + for形式的遍历 + some
const list = [];
data.forEach(item => {
  //判断是否已经存在list数组
  const isHas = list.some(curr => curr.id === item.id);
  if (!isHas) {
    list.push(item);
  }
})
console.log(list)
//[ { id: 1, name: "数据1" },
//  { id: 2, name: "数据2" }, 
//  { id: 3, name: "数据3" } ]

解析:
1、根据id的唯一性,使用some来判断每次遍历出来的数据,是否存在我们新的数组里面

用法2:Object + for形式的遍历 + Object.keys + Array.map
const obj = {};
data.forEach(item => {
  // 判断obj对象是否已存在[item.id]属性
  if (!obj[item.id]) {
    obj[item.id] = item;
  }
});
console.log(obj);
// { 1: { id: 1, name: "数据1" },
//   2: { id: 2, name: "数据2" },
//   3: { id: 3, name: "数据3" } }
Object.keys(obj).map(key => {
  return obj[key];
});
//[ { id: 1, name: "数据1" },
//  { id: 2, name: "数据2" }, 
//  { id: 3, name: "数据3" } ]

解析:
1、根据对象属性的唯一性,我们可以把数据唯一性的id作为obj对象的属性,由此来过滤掉id重复的数据
2、通过Object.keysmap再次把对象给转化成数组

用法3:Map结构 + for形式的遍历 + Map.values
const list = new Map();
data.forEach(item => {
  // 判断map对象是否已存在item.id的key值
  if (!list.has(item.id)) {
    list.set(item.id, item)
  }
});

[...list.values()];
//[ { id: 1, name: "数据1" },
//  { id: 2, name: "数据2" }, 
//  { id: 3, name: "数据3" } ]

解析
1、根据Map结构(值-值)的结构,我们可以把数据的id作为Mapkey值,然后根据Map原型提供的has函数,用来过滤掉重复的数据
2、最后通过Map原型提供的values函数,直接转化成数组

说明:
1、关于Map的values函数返回的是键值的遍历器,而不是像Object.values() 一样返回的是一个值数组,Map返回的这个遍历器可用于for…of遍历写法,详情请看下面链接
2、关于ES6的Map数据结构,大家可以看一下ECMAScript 6 入门-Map数据结构,关于Map结构,我这里就不做另外的说明了

思路2:data1和data2进行对比,过滤重复数据
data2.forEach(item => {
  //判断是否已经存在data1数组
  const isHas = data1.some(curr => curr.id === item.id);
  if (!isHas) {
    data1.push(item);
  }
})
console.log(data1)
//[ { id: 1, name: "数据1" },
//  { id: 2, name: "数据2" }, 
//  { id: 3, name: "数据3" } ]

解析:
1、其实使用到的函数基本是跟上面的差不多,只不过思路不太一样而已,具体我就不做另外用法的举例了,大家有空的可以自己去试试

<=================================================================>

其他补充说明:
其实对于上面的做法,可以有很多其他函数可以代替的,比如:
1、forEach的替换成 for…of 、for,推荐用forEach、for…of,原始写的for循环还是比较麻烦

//forEach
data.forEach(item => { });

//for...of
for (let item of data) { }//常规写法
for (let { id, name } of data) { }//解构写法

//for
for (let i = 0; i < data.length; i++) {
  const item = data[i];
}

2、解法1里面的some可以替换成every、find、filter、findIndex

//查询是否存在id相同的数据
const isHas = list.find(curr => curr.id === item.id);
//返回跟匹配到的数据所在数组的下标
const hadIndex = list.findIndex(curr => curr.id === item.id);
//查询是否全部数据的id都跟当前数据id不相同
const isNotHas = list.every(curr => curr.id !== item.id);
//返回id相同的数据数组
const hasList = list.filter(curr => curr.id === item.id);

if (!isHas || hadIndex !== -1 || isNotHas || hasList.length == 0) {
    list.push(item);
}

2、常量型数组去重

假设有下面数据

const data = [1, 2, 3, 4, 5, 6, 4, 3, 2, 7];
用法1: Set数据结构
const set = new Set(data);
[...set]//[1, 2, 3, 4, 5, 6, 7]

解析
1、根据ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值,因此可以用来过滤掉重复的常量数据

说明:
1、关于ES6的Set数据结构,大家可以看一下ECMAScript 6 入门-Set数据结构,关于Set结构,我这里就不做另外的说明了

其他用法

其实对于常量型的数组,对象型的用法,常量型的基本都适用。比如:

const list = [];
data.forEach(item => {
  //判断是否已经存在list数组
  const isHas = list.some(value => value === item);
  if (!isHas) {
    list.push(item);
  }
})
console.log(list)//[1, 2, 3, 4, 5, 6, 7]

补充说明:
1、对于常量型数组,some可替换的不止every、find、filter、findIndex,还有indexOf、includes,一般对于常量型数组,还是推荐使用indexOf、includes,比如:

//查询list数组是否存在item,没有则返回-1
const isHas = list.indexOf(item) !== -1;

//查询list数组是否存在item,返回一个布尔值
const isHas = list.includes(item);

if (!isHas) {
  list.push(item);
}

数据格式转换、数组筛选、字段提取

关于数据格式转换、字段提取,日常开发是非常、非常、常见的一种数据处理场景,比如:
1、对于前端控件要求的数据结构跟后台返回的数据结构不一致
2、发送请求参数的时候,只需要获取到数据的某个字段的集合,比如id的集合
3、以上出现的场景几乎是在每个项目中100%会出现的

场景1-数据格式转换

有一个下拉框用来展示所有的产品,前端控件要求的数据结构是:

const options = [
  { label: '产品1', value: '1' }
];

而后台给我们返回的数据结构是:

const reuslt = [
  { name: '产品1', id: '1' }
];

这时候我们就需要进行数据结构的转化了

转化方式1
const options = [];
result.forEach(({ id, name }) => {
  options.push({
    label: item.name,
    value: item.id
  })
});
console.log(options);
//[
//  { label: '产品1', value: '1' },
//];

解析:
1、使用forEach函数遍历result数组,再用ES6结构方式提取对应字段
2、遍历result数组,重新组装一个新对象,然后push到一个新数组options里面

转化方式2
const options = result.map(({ id, name }) => {
  return { label: name, value: id };
});
console.log(options);
//[
//  { label: '产品1', value: '1' },
//];

解析:
1、使用map函数遍历result数组,再用ES6结构方式提取对应字段
2、遍历result数组,return一个重新组装一个的对象,最终返回新的数组

2种方式对比:
1、map函数对比forEach函数少了一个push的操作
2、map函数对比forEach函数看起来更加简洁
总结: 推荐使用map函数进行数组的数据结构转化

场景2-数组过滤筛选、字段提取

根据场景1来说,比如说:下来选择框正常是有2种,单选、多选,然后往往我们发送请求的时候,往往其实只需要发送单个id或者是一个id数组,那就可以用下面的做法了:

const options = [
    { label: '产品1', value: '1', checked: true },
    { label: '产品2', value: '2', checked: false },
    { label: '产品3', value: '3', checked: false },
    { label: '产品4', value: '4', checked: false },
];

// 单选情况下
const checked = options.find(item => item.checked)['id'];
const id = checked ? checked.id : '';
console.log(id);//1

//多选情况下获取选中的选项
const checkedList = options.filter(item => item.checked);
// 获取选中项的id集合
const idList = checkedList.map(item => item.id);
console.log(idList);//[1]

解析:
1、单选情况下,用find函数直接获取选中的选项的id
2、单选情况下,如果下拉框没有选中的话,find函数返回的是undefined,则获取id的时候需要先判断find函数返回数据
3、多选情况下,用filter函数获取多个选中的选项,再用map直接return选中选项的id,组成id数组

数据结构转换之进阶《递归Tree树形结构》

在实战中会出现树形组件,而树形组件需要的就是树形的数据结构了,而后台不一定返回给你的就是树形结构了,这时候就是需要我们自己进行转化了。
假设后台返回的数据结构是:

const data = [
    { id: '1', name: '产品1', parentId: '' },
    { id: '2', name: '产品1-1', parentId: '1' },
    { id: '3', name: '产品1-2', parentId: '1' },
    { id: '4', name: '产品4', parentId: '' },
    { id: '5', name: '产品4-1', parentId: '4' }
];

这个时候就需要用到我们的filter、map函数和递归函数了

const getTreeData = (dataList, parentId) => {
    // 获取所有匹配到parentId的数据
    const list = dataList.filter(item => item.parentId === parentId);
    // 递归再次返回带children,此处用了ES6解构写法
    return list.map(item => ({ ...item, children: getTreeData(dataList, item.id) }));
}
const treeData = getTreeData(data, '');
console.log(treeData);
// [
//     {
//         id: '1', name: '产品1', parentId: '',
//         children: [
//             { id: '2', name: '产品1-1', parentId: '1', children: [] },
//             { id: '3', name: '产品1-2', parentId: '1', children: [] }
//         ]
//     },
//     {
//         id: '4', name: '产品2', parentId: '',
//         children: [
//             { id: '5', name: '产品2-1', parentId: '4', children: [] }
//         ]
//     },
// ]

解析:
1、getTreeData 递归函数接收2个参数,1是后台返回给我们的data数组,2是进行数据匹配的一个条件parentId
2、一开始第二个参数传了 ‘’ 空字符进去,就是循环遍历顶级的父类。
3、第一次第一步filter的结果得出的顶级父类数组就是:

[
    { id: '1', name: '产品1', parentId: '' },
    { id: '4', name: '产品2', parentId: '' }
]

4、第一次第二步map函数就是往2个顶级父类中加多了children属性,而children属性值就是再一次递归函数,进入第二次的递归过程。
5、递归终结的条件就是map函数对空数组不会遍历,也就是当data数组中匹配不到传进来parentId的时候,filter返回的结果就是空数组了,也是递归的终结

总结:
以上基本就是实战中,我经常遇到的几种场景和处理方式,写出来也是自己的一次总结和分享
另外以上也是可以用其他方式函数代替,只不过既然有更简洁的函数提供给我们使用,干嘛还要去写那么多麻烦的函数咧?
最后其实还有个几乎万能的函数《reduce》,在这里我没写出来,因为它可以当上面很多函数来用的,关于这个我后续会单独写个reduce函数的续章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值