关于JavaScript 数组你应该知道的事情(推荐)
首先做一个粗体声明:循环经常是无用的,并且使得代码很难阅读。
当谈到迭代一个数组的时候,无论你想去查找元素,排序或者任何其他的事,都有可能存在一个数组的方法供你使用。
然而,尽管它们有用,但其中一些仍然不被人了解。我会努力为你展示一些有用的方法。把这篇文章当做对JavaScript数组方法的指引吧。
注意:在开始之前,不得不了解一件事:我比较偏爱函数式编程。所以我倾向于使用的方法不会直接改变原来的数组。这种方法,我避免了副作用。我不是说不应该改变数组,但至少要了解那些方法会改变,那些会有副作用。副作用导致不想要的改变,而不想要的改变带来bugs!
了解到这里,我们可以开始正文了。
必不可少的
当跟数组打交道时,有四件事你应该清楚:map,filter,reduce和展开操作符。它们富有力量。
map
你可以在很多种情况下使用它。基本地,每次你需要修改数组的元素时,考虑使用map。
它接受一个参数:一个方法,在每一个数组元素上调用。然后返回一个新的数组,所以没有副作用。
constnumbers=[1,2,3,4] constnumbersPlusOne=numbers.map(n=>n+1)//每个元素+1 console.log(numbersPlusOne)//[2,3,4,5]
你也能创建一个新数组,用于保留对象的一个特殊属性:
constallActivities=[ {title:'Myactivity',coordinates:[50.123,3.291]}, {title:'Anotheractivity',coordinates:[1.238,4.292]}, //etc. ] constallCoordinates=allActivities.map(activity=>activity.coordinates) console.log(allCoordinates)//[[50.123,3.291],[1.238,4.292]]
所以,请记住,当你需要去转换数组时,考虑使用map。
filter
这个方法的名字在这里十分准确的:当你想去过滤数组的时候使用它。
如同map所做,它接受一个函数作为它的唯一参数,在数组的每个元素上调用。这个方法返回一个布尔值:
- true如果你需要在数组中保留元素
- false如果你不想保留它
接着你会得到一个带有你想要保留的元素的新数组。
举个例子,你可以在数组中只保留奇数:
constnumbers=[1,2,3,4,5,6] constoddNumbers=numbers.filter(n=>n%2!==0) console.log(oddNumbers)//[1,3,5]
或者你可以在数组中移除特殊的项:
constparticipants=[ {id:'a3f47',username:'john'}, {id:'fek28',username:'mary'}, {id:'n3j44',username:'sam'}, ] functionremoveParticipant(participants,id){ returnparticipants.filter(participant=>participant.id!==id) } console.log(removeParticipant(participants,'a3f47'))//[{id:'fek28',username:'mary'},{id:'n3j44',username:'sam'}];
reduce
个人认为是最难理解的方法。但是如果你一旦掌握它,很多疯狂的事情你都可以用它做到。
基本地,reduce使用有值的数组然后组合成一个新的值。它接受两个参数,一个回调方法就是我们的reducer和一个可选的初始化的值(默认是数组的第一个项)。这个reducer自己使用四个参数:
- 累计:在你的reducer中累积的返回值
- 当前数组的值
- 当前索引
- 当前调用reduce的数组
大多数时候,你只需要使用前两个参数:累计值和当前值。
抛开这些理论。来看看常见的一个reduce的例子。
constnumbers=[37,12,28,4,9] consttotal=numbers.reduce((total,n)=>total+n) console.log(total)//90
在第一个遍历时,这个累计值,也就是total,使用了初始化为37的值。它返回的值是37+n并且n等于12,因此得到49.在第二次遍历时,累加值是49,返回值是49+28=77。如此继续直到第四次。
reduce是很强大的,你可以实际使用它去构建很多数组的方法,比如map或者filter:
constmap=(arr,fn)=>{ returnarr.reduce((mappedArr,element)=>{ return[...mappedArr,fn(element)] },[]) } console.log(map([1,2,3,4],n=>n+1))//[2,3,4,5] constfilter=(arr,fn)=>{ returnarr.reduce((filteredArr,element)=>{ returnfn(element)?[...filteredArr]:[...filteredArr,element] },[]) } console.log(filter([1,2,3,4,5,6],n=>n%2===0))//[1,3,5]
根本上看,我们给reduce一个初始默认值[]:我们的累计值。对于map,我们运行一个方法,它的结果是累加到最后,多亏了展开操作符(不必担心,后面讨论)。对于filter,几乎是相似的,除了我们在元素上运行过滤函数。如果返回true,我们返回前一个数组,否则在数组最后添加当前元素。
我们来看一个更高级的例子:深度展开数组,也就是说把[1,2,3,[4,[[[5,[6,7]]]],8]]样的数组转换成[1,2,3,4,5,6,7,8]样的。
functionflatDeep(arr){ returnarr.reduce((flattenArray,element)=>{ returnArray.isArray(element) ?[...flattenArray,...flatDeep(element)] :[...flattenArray,element] },[]) } console.log(flatDeep([1,2,3,[4,[[[5,[6,7]]]],8]]))//[1,2,3,4,5,6,7,8]
这个例子有点像map,除了我们用到了递归。我不想去解释这个用法,它超出了这篇文章的范围。但是,如果你想了解更多的关于递归的知识,请参考这篇优质的文章。
展开操作(ES2015)
我知道这不是一个方法。但是,在处理数组时,使用展开操作可以帮助你做很多事情。事实上,你可以在另一个数组中使用它展开一个数组的值。从这一点来说,你可以复制一个数组,或者连接多个数组。
constnumbers=[1,2,3] constnumbersCopy=[...numbers] console.log(numbersCopy)//[1,2,3] constotherNumbers=[4,5,6] constnumbersConcatenated=[...numbers,...otherNumbers] console.log(numbersConcatenated)//[1,2,3,4,5,6]
注意::展开操作符对原数组做了一次浅拷贝。但什么是浅拷贝?