数组
在业务中是一个非常高频的API,在业务中基本都有用它,必不可少,本文是笔者一篇关于数组常用API
的总结,希望看完在项目中有所帮助。
正文开始...
前置
首先我们看下数组本身有哪些方法
console.log(JSON.stringify(Reflect.ownKeys(Array.prototype), null, 2))
结果:
[ "length", "constructor", "at", "concat", "copyWithin", "fill", "find", "findIndex", "lastIndexOf", "pop", "push", "reverse", "shift", "unshift", "slice", "sort", "splice", "includes", "indexOf", "join", "keys", "entries", "values", "forEach", "filter", "flat", "flatMap", "map", "every", "some", "reduce", "reduceRight", "toLocaleString", "toString", "findLast", "findLastIndex", ]
reduce
这是一个项目上非常有用,但是代码看起来不是很直白的一个API
场景
我需要根据数组中的某个值,用对象与原数组建立映射关系
var sourceArr = [ { name: 'Maic', age: 18, arr: ['a', 'b'] }, { name: 'Tom', age: 20, arr: ['a', 'b', 'c'] }, { name: 'Jack', age: 15, arr: ['e', 'd', 'f'] } ]
现在我想通过对象
访问数组的某个name
或者value
就能找到当前原数据的item
,前置条件name
和value
不会为相同
function getMap(key, arr) { return arr.reduce((prev, cur) => { if (key) { prev[cur[key]] = cur; return prev } }, {}) } /* // getMap('name', sourceArr) /* { Jack: {name: 'Jack', age: 15, arr: Array(3)} Maic: {name: 'Maic', age: 18, arr: Array(2)} Tom: {name: 'Tom', age: 20, arr: Array(3)} } */ console.log(getMap('name', sourceArr)['Maic']) /* { name: 'Maic', age: 18, arr: ['a', 'b'] }, */ console.log(getMap('age', sourceArr)[15]) /* { name: 'Jack', age: 15, arr: ['e', 'd', 'f'] } */ console.log(getMap('arr', sourceArr)['a,b']) /* { name: 'Maic', age: 18, arr: ['a', 'b'] }, */
我们可以让这个方法getMap
变成更通用,只需要挂载原型上即可
... Array.prototype.$getMap = function(key) { return this.reduce((prev, cur) => { if (key) { prev[cur[key]] = cur; return prev } }, {}) } sourceArr.$getMap('name') /* { Jack: {name: 'Jack', age: 15, arr: Array(3)} Maic: {name: 'Maic', age: 18, arr: Array(2)} Tom: {name: 'Tom', age: 20, arr: Array(3)} } */
通过以上例子,我们分析一下reduce
这个在数组中所谓的累计计算
,我们以最简单的方式来深刻理解reduce
这个方法
const sourceArr = [ { name: 'Maic', age: 18, arr: ['a', 'b'] }, { name: 'Tom', age: 20, arr: ['a', 'b', 'c'] }, { name: 'Jack', age: 15, arr: ['e', 'd', 'f'] } ] const res = sourceArr.reduce((prev, cur) => { prev[cur.name] = cur; return prev }, {})
arr.reduce(callback, init)
的第一个参数是回调函数,第二参数previnit
的值,callback
的prev
就是{}
,cur
是当前的数组的item
第一次累计的结果prev
的值是:
{ 'Maic': { name: 'Maic', age: 18, arr: ['a', 'b'] } }
这个结果会当成第二次累计的prev
值,记住cur
是当前原元素累计次数的item
,比如从下标0次开始累计,那么cur
就是数组的第一个item
第二次累计的结果就是
{ 'Maic': { name: 'Maic', age: 18, arr: ['a', 'b'] }, 'Tom': { name: 'Tom', age: 20, arr: ['a', 'b', 'c'] } }
依次类推...
所以我通过对象,将数组的值作为对象的key
,建立对象与原数据的对应关系,用reduce
这个方法可以快捷的达到这样的需求效果,关于数组reduce
后续会单独写一篇文章总结更多在实际业务上的一些思考。也可参考官方文档MDN
讲解reduce
[1]这篇好文章
有人说reduce
实现这功能有点秀了,for
循环不是更好理解吗
forEach
forEach
也是一个循环数组的的方法,循环方法我们知道在js
中for..of
,for(let i=0;i<len;i++)
或者while
条件,这些都是可以条件中断,但是forEach
不能中断【非常规操作除外,比如throw抛出异常是可以中断forEach
的】
我们用同样的一个例子来实现reduce
一样的功能
... function getMap2 (key, arr) { const res = {} arr.forEach(v => { res[v[key]] = v; }) return res; } getMap2('name', sourceArr)['Maic']
当然是可以的,条条大路通罗马,forEach
貌似看起来比reduce
写的那段代码阅读负担要小得多,但是同样的效果forEach
执行效率也比reduce
更高点
具体可以看下这张图数据
所以复杂的事情,尽量简单化,没有好坏高低之分,对于搬砖工来说,哪种熟悉就用哪个了。
push
这是一个比较常用的方法,也是向数组中添加数据
场景
:假设现在有一个需求,如何将一个一维数组变成一个树结构,并且还要按照指定分类进行分组
原数据大概就是这样
var sourcesData = [ { bookType: '文学类', type: 1, bookName: '基督山伯爵', id: 'x123' }, { bookType: '财商类', type: 2, bookName: '穷爸爸与富爸爸', id: 'x45622' }, { bookType: '经济学', type: 3, bookName: '货币战争', id: 'ssxdede' }, { bookType: '文学类', type: 1, bookName: '百年孤独', id: '1234562sx' } ]
后端给的数据是一维的,我们需要变成一个tree
结构进行分类
const transformTree = (sourceArr, result) => { // 1、先根据type字段进行分组 const typeData = [1, 2, 3].map(type => sourceArr.filter(v => v.type === type * 1)) // 2、分别含有type字段进行分类后 for (data of typeData) { data.forEach(item => { // 3、根据bookType进行归组,文件夹分类,同一文件夹的归到一类去 const target = result.find(v => v.label === item.bookType); if (target) { // 如果找到了就原数组数据添加到children里去 target.children.push({ label: item.bookName, ...item }) } else { result.push({ label: item.bookType, children: [ { ...item, label: item.bookName } ] }) } }) } return result } console.log('push:res', JSON.stringify(transformTree(sourcesData, []), null, 2));
打印的结果:
[ { "label": "文学类", "children": [ { "bookType": "文学类", "type": 1, "bookName": "基督山伯爵", "id": "x123", "label": "基督山伯爵" }, { "label": "百年孤独", "bookType": "文学类", "type": 1, "bookName": "百年孤独", "id": "1234562sx" } ] }, { "label": "财商类", "children": [ { "bookType": "财商类", "type": 2, "bookName": "穷爸爸与富爸爸", "id": "x45622", "label": "穷爸爸与富爸爸" } ] }, { "label": "经济学", "children": [ { "bookType": "经济学", "type": 3, "bookName": "货币战争", "id": "ssxdede", "label": "货币战争" } ] } ]
因此我们就将一个一维数组变成了一个tree
结构
我们可以将上面一段forEach
改成reduce
,感受下理解的难度,最后的效果是一样,但是reduce
对新手不太友好,这里就是为了使用而使用,好像没太必要
const transformTree2 = (sourceArr, result) => { // 1、先根据type字段进行分组 const typeData = [1, 2, 3].map(type => sourceArr.filter(v => v.type === type * 1)) // 2、分别含有type字段进行分类后 for (data of typeData) { data.reduce((prev, cur) => { // 3、根据bookType进行归组,文件夹分类,同一文件夹的归到一类去 const target = result.find(v => v.label === cur.bookType); if (target) { target.children.push({ label: cur.bookName, ...cur }) } else { result.push({ label: cur.bookType, children: [ { ...cur, label: cur.bookName } ] }) } }, sourceArr[0]) } return result } console.log(transformTree2(sourcesData, []))
some
这是一个只要条件有一个满足就返回true,否则就返回false场景
: 我需要在原数组大于某个值,一旦满足,就返回true
const arraySome = (arr, num) => { return arr.some(v => v > num) } console.log('some:', arraySome([1, 2, 3], 2), arraySome([4, 5, 6], 7)) // true, false
every
恰好与some
相反,必须所有条件满足,才会返回true
场景
: 在业务中你想一个原数据的每一项都满足一个指定条件,此时会返回true,否则就是false
const arrayEvery = (arr, num) => { return arr.every(v => v.includes(num)) } console.log('every:', arrayEvery(['abc', 'cdabc', 'efg'], 'ab'), arrayEvery(['abc', 'cdabc', 'aefg'], 'a')) // false true
at
比较罕见,与通过下标去值等价
const arrayAt = (arr = [], index) => { return arr.at(index) }
concat
在原有数组浅拷贝一份新的数据,然后在新数据添加对应的内容
/** * arr: [1,2,3] * concat: 在原数组基础上浅拷贝一份新的数据,然后在新数据上追加对应的内容 * 示例:ret = arr.concat(4) ----- ret: [1,2,3,4] * ret = arr.concat('a', {a:'Maic'}, ['abc',{a: 'Tom'}]) ret: [1,2,3,'a',{a:'Maic'},'abc', {a: 'Tom'}] * ret = arr.concat(1).concat(2) [1,2,3,1,2] * 场景:不太想影响原数据,又想在原数据上添加数据时,但是注意这个方法是一个浅拷贝,如果数组中是引用数据类型,修改新值会影响原有的值 * */ const arrayConcat = () => { const arr = [1, 2, 3, { name: 'Maic' }] const newArr = arr.concat('abc') newArr[3].name = 'Tom' const arr2 = arr.concat('a', { a: 'Maic' }, ['abc', { a: 'Tom' }]) const arr3 = arr.concat([1, 2, 3]) const arr4 = [1, 2, 3].concat(4).concat(5) return { newArr, arr, arr2, arr3, arr4 } } console.log('concat:', arrayConcat())
fill
填充一份相同的数据
场景
:你想mock一份测试数据
const arrayFill = (num, val) => { return Array(num).fill(val) } console.log('fill:', arrayFill(3, 'Maic')) /* [ 'Maic', 'Maic', 'Maic' ] */
find
/** * find: 寻找数组的item,并返回其寻找到的结果, 如果没有找到就返回undefined * 场景:需要根据某个条件值找到数据中的当前item数据时 */ const arrayFind = (sourceData, key, target) => { return sourceData.find(v => v[key] === target) } console.log('find:', arrayFind([{ name: 'Maic', age: 18 }, { name: 'Tom', age: 25 }], 'name', 'Maic')) // {name: 'Maic', age: 18}
findIndex
寻找原数据匹配的目标值下标
/** * findIndex: 寻找目标值的当前索引,如果没找到就返回-1 * 场景:在你根据某个条件想获取当前条件的索引值,比如进行删除,或者插入,替换等操作 */ const arrayFindIndex = (sourceData, key, target) => { return sourceData.findIndex(v => v[key] === target) } console.log('findIndex', arrayFindIndex([{ name: 'Maic', age: 18 }, { name: 'Tom', age: 25 }], 'name', 'Jack'))
lastIndexOf
找到目标元素的当前索引
/** * lastIndexOf: 找到元素的当前下标索引 * 场景:功能与findIndex类似,根据其值寻找目标值的当前下标索引 */ const arrayLastIndexOf = (sourceData, val) => { return sourceData.lastIndexOf(val) } console.log('lastIndexOf', arrayLastIndexOf(['a', 'b', 'c', 'd'], 'b')) // 1
pop
获取数组元素的最后一只元素的值,会改变原数组的长度,每一次pop操作将会把数组的最后一只值弹出去,原数组长度会减一
const arrayPop = function (sourceData) { return sourceData.pop() } console.log('pop:', arrayPop(['a', 'b', 'c'])) // c
reverse
将原数据进行倒叙排列
const arrayReverse = (sourceData) => { return sourceData.reverse() } console.log('reverse', arrayReverse([1, 2, 3, 4])) // [4,3,2,1]
shift
获取数组的第一个元素,会改变原数组的长度,会改变原数组场景
:模拟队列
const arrayShift = (sourceData) => { return { data: sourceData.shift(), sourceData } } console.log('shift', arrayShift([1, 2, 3, 4])) // {data:1, sourceData: [2,3,4]}
unshift
向原数据添加数据,每次操作都会往数组的首位添加,会改变原数组的长度,返回值是当前数组的长度
const arrayUnshift = (sourceData, val) => { return { result: sourceData.unshift(val), sourceData } } console.log('unshift:', arrayUnshift([1, 2, 3], 'a'))
slice
获取原数据指定索引范围的值,不会影响原有值
/** * slice: 获取原数据指定索引范围的值,不会影响原有值 * 场景:应用很多 * arr: [1,2,3,4] * * arr.slice(0) --- [1,2,3,4] 浅拷贝 * arr.slice(1) --- [2,3,4] * * arr.slice(1,3) --- [2,3] * * arr.slice(-1) ---[4] * arr.slice(-2)----[3,4] * */ const arraySlice = (sourceArr = []) => { const arr1 = sourceArr.slice(0); const arr2 = sourceArr.slice(1); const arr3 = sourceArr.slice(1, 3); const arr4 = sourceArr.slice(-1); const arr5 = sourceArr.slice(-2); return { arr1, arr2, arr3, arr4, arr5 } } console.log('slice:', arraySlice([1, 2, 3, 4])); /* slice: { arr1: [ 1, 2, 3, 4 ], arr2: [ 2, 3, 4 ], arr3: [ 2, 3 ], arr4: [ 4 ], arr5: [ 3, 4 ] } */
sort
对数组进行排序
/** * sort: 排序 * arr.sort((a, b) => a - b) // 升序 * arr.sort((a,b) => b-a) // 降序 * 场景:对数据的某个字段进行排序 */ const arraySort = (sourceArr = []) => { const upSort = sourceArr.sort((a, b) => a - b) const downSort = sourceArr.sort((a, b) => b - a) return { upSort, downSort } }
splice
对原数组进行删除
、替换
、截取
操作,会影响原数组的值
const arraySplice = () => { const arr1 = [1, 2, 3, 4].splice(1); // [2,3,4] const arr2 = [1, 2, 3, 4].splice(0, 2); // [1,2] const arr3 = [1, 2, 3, 4].splice(2, 1); // [1,2,4] 删除了索引为2的元素,返回剩下的元素 const arr4 = [1, 2, 3, 4].splice(-1); // [4] return { arr1, arr2, arr3, arr4 } } console.log('splice:', arraySplice())
filter
过滤数组操作,根据某个条件,返回一个过滤结果的数组
/** * filter: 根据条件进行过滤,返回过滤后的结果 * 场景:需要过滤原数据中某些值时 */ const arrayFilter = (sourceData, val) => { return sourceData.filter(v => v === val) } console.log('filter:', arrayFilter([1, 2, 3, 4], 4))
map
根据原数组重新返回一个新的数组
/* * map: 在原有基础上重新返回一个新数组 */ const arrayMap = (sourceArr) => { return sourceArr.map(v => v + 1) } console.log('map:', arrayMap([1, 2, 3])) // [2,3,4]
flatMap
场景
:可以根据原数组组合成一个你想要的数据结构
比如原数据有不想要的字段
const arrayFlatMap = (sourceArr, arr) => { return { source: sourceArr.flatMap(v => typeof v === 'number' ? [v] : []), narr: arr.flatMap(v => [{ name: v.name, value: v.value }]) } } console.log('flatMap:', arrayFlatMap([1, 2, [3, 4]], [{ id: 1, name: 'Maic', value: 0 }, { id: 2, name: 'Tom', value: 1 }])) /** * flatMap: { source: [ 1, 2 ], narr: [ { name: 'Maic', value: 0 }, { name: 'Tom', value: 1 } ] } */
toString
将数组转换成字符串
/** * toString: 将数组进行转换 * 场景:想将一个数组变成字符串 */ const arrayTostring = (sourceArr) => { return sourceArr.toString() } console.log('toString:', arrayTostring([1, 2, 3, 4])) /* 1,2,3,4 */
includes
判断一个数组是否包含某个元素
/** * includes: 包含 * 场景:一个元素是否在数组中存在 */ const arrayIncludes = (arr, val) => { return arr.includes(val) } console.log('arrayIncludes:', arrayIncludes([1, 2, 3], 1))
indexOf
获取一个元素的下标,如果没有就返回-1
const arrayIndexOf = (arr, val) => { return arr.indexOf(val) } console.log(arrayIndexOf([1, 2, 3], 1));
join
将一个数组以特殊字符进行拼接,变成一个字符串
const arrayJoin = (arr, split) => { return arr.join(split) } console.log(arrayJoin([1,2,3], '-')) // join: 1-2-3
总结
- 利用
reduce
如何建立数组与对象的映射关系,还有如何将一个一维数组构建成一个tree
结构 - 分析了
reduce
累计计算器这个API
的使用 - 常用的数组方法解析,以及实际应用场景
- 本文示例code example[2]