数组是最常见的数据结构之一,我们需要绝对自信地使用它。在这里,我将列出JavaScript中最重要的几个数组常用操作片段,包括数组长度、替换元素、去重以及许多其他内容。1、数组长度 大多数人都知道可以像这样得到数组的长度:constarr〔1,2,3〕;console。log(arr。length);3 有趣的是,我们可以手动修改长度。这就是我所说的:constarr〔1,2,3〕;arr。length2;arr。forEach(iconsole。log(i));12 甚至创建指定长度的新数组:constarr〔〕;arr。length100;console。log(arr)〔undefined,undefined,undefined。。。〕 这不是一个很好的实践,但是值得了解。 我们常常需要清空数组时候会使用:constarr〔1,2〕;arr。length0;console。log(arr)〔〕 如果arr的值是共享的,并且所有参与者都必须看到清除的效果,那么这就是你需要采取的方法。 但是,JavaScript语义规定,如果减少数组的长度,则必须删除新长度及以上的所有元素。 而且这需要花费时间(除非引擎对设置长度为零的特殊情况进行了优化)。实际上,一个性能测试表明,在所有当前的JavaScript引擎上,这种清除方法更快。2、替换数组元素 有几种方法可以解决这个问题。如果需要替换指定索引处的元素,请使用splice:constarr〔1,2,3〕;arr。splice(2,1,4);将索引2开始的1元素更改为4console。log(arr);〔1,2,4〕arr。splice(0,2,5,6)将索引0开始的2个元素更改为5和6console。log(arr);〔5,6,4〕 splice在数组删除有更多的说明 如果你需要根据项目的内容替换项目,或者必须创建一个新数组,请使用map:constarr〔1,2,3,4,5,6〕;所有奇数的平方constarr2arr。map(itemitem20?item:itemitem);console。log(arr2);〔1,2,9,4,25,6〕; map接受函数作为其参数。它将对数组中的每个元素调用该函数一次,并生成一个新的函数返回的项数组。 关于map有个经典的面试题:〔1,2,3,4,5〕。map(parseInt)?3、过滤数组 在某些情况下,你需要删除数组中的某些元素,然后创建一个新的元素。在这种情况下,使用在ES5中引入的很棒的filter方法:constarr〔1,2,3,4,5,6,7〕;过滤掉所有奇数constarr2arr。filter(itemitem20);console。log(arr2);〔2,4,6〕; filter的工作原理与map非常相似。向它提供一个函数,filter将在数组的每个元素上调用它。如果要在新数组中包含此特定元素,则函数必须返回true,否则返回false。4、合并数组 如果你想将多个数组合并为一个数组,有两种方法。 Array提供了concat方法:constarr1〔1,2,3〕;constarr2〔4,5,6〕;constarr3arr1。concat(arr2);console。log(arr3);〔1,2,3,4,5,6〕 ES6中引入了spreadoperator,一种更方便的方法:constarr1〔1,2,3〕;constarr2〔4,5,6〕;constarr3〔。。。arr1,。。。arr2〕;console。log(arr3);〔1,2,3,4,5,6〕 还有一种比较奇特方法:constarr1〔1,2,3〕;constarr2〔4,5,6〕;Array。prototype。push。apply(arr1,arr2);console。log(arr1);〔1,2,3,4,5,6〕 上面2种通用的方法,都不会改变原数组,最后一种奇特方法,会改变push的原数组,谨慎使用。 Array。prototype。push。apply和concat对比:数据上万情况下,两者性能相差毫秒个位数级别Array。prototype。push。apply数组长度有限制,不同浏览器不同,一般不能超过十万,concat无限制Array。prototype。push。apply会改变原数组,concat不会 正常情况下我们都应该使用concat和spreadoperator,有种情况下可以使用,如果频繁合并数组可以用Array。prototype。push。apply。5、复制数组 总所周知,定义数组变量存储不是数组值,而只是存储引用。这是我的意思:constarr1〔1,2,3〕;constarr2arr1;arr2〔0〕4;arr2〔1〕2;arr2〔2〕0;console。log(arr1);〔4,2,0〕 因为arr2持有对arr1的引用,所以对arr2的任何更改都是对arr1的更改。constarr1〔1,2,3〕;constarr2arr1。slice(0);arr2〔0〕4;arr2〔1〕2;arr2〔2〕0;console。log(arr1);〔1,2,3〕console。log(arr2);〔4,2,0〕 我们也可以使用ES6的spreadoperator:constarr1〔1,2,3〕;constarr2〔。。。arr1〕;arr2〔0〕4;arr2〔1〕2;arr2〔2〕0;console。log(arr1);〔1,2,3〕console。log(arr2);〔4,2,0〕 也可以使用前面合并使用的concat方法constarr1〔1,2,3〕;constarr2〔〕。concat(arr1);arr2〔0〕4;arr2〔1〕2;arr2〔2〕0;console。log(arr1);〔1,2,3〕console。log(arr2);〔4,2,0〕 注意:如果想要了解更多的数组复制,请查询数组深拷贝和浅拷贝相关资料,这里只实现了浅拷贝。6、数组去重 数组去重是面试经常问的,数组去重方式很多,这里介绍比较简单直白的三种方法: 可以使用filter方法帮助我们删除重复数组元素。filter将接受一个函数并传递3个参数:当前项、索引和当前数组。constarr1〔1,1,2,3,1,5,9,4,2〕;constarr2arr1。filter((item,index,arr)arr。indexOf(item)index);console。log(arr2);〔1,2,3,5,9,4〕 可以使用reduce方法从数组中删除所有重复项。然而,这有点棘手。reduce将接受一个函数并传递2个参数:数组的当前值和累加器。 累加器在项目之间保持相同,并最终返回:constarr1〔1,1,2,3,1,5,9,4,2〕;constarr2arr1。reduce((acc,item)acc。indexOf(item)1?〔。。。acc,item〕:acc,〔〕初始化当前值);console。log(arr2);〔1,2,3,5,9,4〕 可以使用ES6中引入的新数据结构set和spreadoperator:constarr1〔1,1,2,3,1,5,9,4,2〕;constarr2〔。。。(newSet(arr1))〕;console。log(arr2);〔1,2,3,5,9,4〕 还有很多其他去重方式,比如使用{}for。7、转换为数组 有时我们必须将一些其它数据结构,如集合或字符串转换为数组。 类数组:函数参数,dom集合Array。prototype。slice。call(arguments);Array。prototype。concat。apply(〔〕,arguments); 字符串:console。log(string。split());〔s,t,r,i,n,g〕console。log(Array。from(string));〔s,t,r,i,n,g〕 集合:console。log(Array。from(newSet(1,2,3)));〔1,2,3〕console。log(〔。。。(newSet(1,2,3))〕);〔1,2,3〕8、数组遍历 数组遍历方式很多,有底层的,有高阶函数式,我们就来介绍几种: for:constarr〔1,2,3〕;for(leti0;iarr。length;i){console。log(arr〔i〕);}123 forin:constarr〔1,2,3〕;for(letiinarr){if(arr。hasOwnProperty(i)){console。log(arr〔i〕);}}123 forof:constarr〔1,2,3〕;for(letiofarr){console。log(i);}123 forEach:〔1,2,3〕。forEach(iconsole。log(i))123 while:constarr〔1,2,3〕;leti1;constlengtharr。length;while(ilength){console。log(arr〔i〕)}123 迭代辅助语句:break和continuebreak语句是跳出当前循环,并执行当前循环之后的语句continue语句是终止当前循环,并继续执行下一次循环 上面方式中,除了forEach不支持跳出循环体,其他都支持。高阶函数式方式都类似forEach。 性能对比:whileforforofforEachforin 如果是编写一些库或者大量数据遍历,推荐使用while。有名的工具库lodash里面遍历全是while。正常操作,forof或者forEach已经完全满足需求。 下面介绍几种高级函数式,满足条件为true立即终止循环,否则继续遍历到整个数组完成的方法:ES5〔1,2,3〕。some((i)i1);ES6〔1,2,3〕。find((i)i1);〔1,2,3〕。findIndex((i)i1); 其他高阶函数式方法,例如forEachmapfilterreducereduceRighteverysort等,都是把整个数组遍历。9、扁平化多维数组 这个功能说不是很常用,但是有时候又会用到: 二维数组:constarr1〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕;constarr2〔〕。concat。apply(〔〕,arr1);console。log(arr2);〔1,2,3,4,5,6,7,8,9〕 三维数组:constarr1〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕,〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕〕;constarr2〔〕。concat。apply(〔〕,arr1);console。log(arr2);〔1,2,3,4,5,6,7,8,9,〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕 concat。apply方式只能扁平化二维数组,在多了就需要递归操作。functionflatten(arr){returnarr。reduce((flat,toFlatten){returnflat。concat(Array。isArray(toFlatten)?flatten(toFlatten):toFlatten);},〔〕);}constarr1〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕,〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕〕;constarr2flatten(arr1);console。log(arr2);〔1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9〕 ES6(ES2019)给我们提供一个flat方法:constarr1〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕;constarr2arr1。flat();console。log(arr2);〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕 默认只是扁平化二维数组,如果想要扁平化多维,它接受一个参数depth,如果想要展开无限的深度使用Infinity:constarr1〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕,〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕〕;constarr2arr1。flat(Infinity);console。log(arr2);〔1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9〕 还有一种面试扁平化二维数组方式:constarr1〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕,〔〔1,2,3〕,〔4,5,6〕,〔7,8,9〕〕〕;constarr2arr1。toString()。split(,)。map(nparseInt(n,10));console。log(arr2);〔1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9〕10、数组添加 如何从数组中添加元素? 我们可以使用push从数组末尾添加元素,使用unshift从开头添加元素,或者使用splice从中间添加元素。concat方法可创建带有所需项目的新数组,这是一种添加元素的更高级的方法。 从数组的末尾添加元素:constarr〔1,2,3,4,5,6〕;arr。push(7)console。log(arr);〔1,2,3,4,5,6,7〕 从数组的开头添加元素:constarr〔1,2,3,4,5,6〕;arr。unshift(0)console。log(arr);〔0,1,2,3,4,5,6〕 push方法的工作原理与unshift方法非常相似,方法都没有参数,都是返回数组更新的length属性。它修改调用它的数组。 使用splice添加数组元素: 只需要把splice,第二个参数设为0即可,splice在数组删除有更多的说明constarr〔1,2,3,4,5〕;arr。splice(1,0,10)console。log(arr);〔1,10,2,3,4,5〕 使用concat添加数组元素:constarr1〔1,2,3,4,5〕;constarr2arr1。concat(6);console。log(arr2);〔1,2,3,4,5,6〕11、数组删除 数组允许我们对值进行分组并对其进行遍历。我们可以通过不同的方式添加和删除数组元素。不幸的是,没有简单的Array。remove方法。 那么,如何从数组中删除元素? 除了delete方式外,JavaScript数组还提供了多种清除数组值的方法。 我们可以使用pop从数组末尾删除元素,使用shift从开头删除元素,或者使用splice从中间删除元素。 filter方法可创建带有所需项目的新数组,这是一种删除不需要的元素的更高级的方法。 从数组的末尾删除元素: 通过将length属性设置为小于当前数组长度,可以从数组末尾删除数组元素。索引大于或等于新长度的任何元素都将被删除。constarr〔1,2,3,4,5,6〕;arr。length4;console。log(arr);〔1,2,3,4〕 pop方法删除数组的最后一个元素,返回该元素,并更新length属性。pop方法会修改调用它的数组,这意味着与使用delete不同,最后一个元素被完全删除并且数组长度减小。constarr〔1,2,3,4,5,6〕;arr。pop();console。log(arr);〔1,2,3,4,5〕 从数组的开头删除元素: shift方法的工作原理与pop方法非常相似,只是它删除了数组的第一个元素而不是最后一个元素。constarr〔1,2,3,4,5,6〕;arr。shift();console。log(arr);〔2,3,4,5,6〕 shift和pop方法都没有参数,都是返回已删除的元素,更新剩余元素的索引,并更新length属性。它修改调用它的数组。如果没有元素,或者数组长度为0,该方法返回undefined。 使用splice删除数组元素: splice方法可用于从数组中添加、替换或删除元素。 splice方法接收至少三个参数:start:在数组中开始删除元素的位置deleteCount:删除多少个元素(可选)items。。。:添加元素(可选) splice可以实现添加、替换或删除。 删除: 如果deleteCount大于start之后的元素的总数,则从start后面的元素都将被删除(含第start位)。 如果deleteCount被省略了,或者它的值大于等于array。lengthstart(也就是说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。 如果deleteCount是0或者负数,则不移除元素。这种情况下,至少应添加一个新元素。constarr1〔1,2,3,4,5〕;arr1。splice(1);console。log(arr1);〔1〕;constarr2〔1,2,3,4,5〕;arr2。splice(1,2)console。log(arr2);〔1,4,5〕constarr3〔1,2,3,4,5〕;arr3。splice(1,1)console。log(arr3);〔1,3,4,5〕 添加: 添加只需要把deleteCount设置为0,items就是要添加的元素。constarr〔1,2,3,4,5〕;arr。splice(1,0,10)console。log(arr);〔1,10,2,3,4,5〕 替换: 添加只需要把deleteCount设置为和items个数一样即可,items就是要添加的元素。constarr〔1,2,3,4,5〕;arr。splice(1,1,10)console。log(arr);〔1,10,3,4,5〕 注意:splice方法实际上返回两个数组,即原始数组(现在缺少已删除的元素)和仅包含已删除的元素的数组。如果循环删除元素或者多个相同元素,最好使用倒序遍历。 使用delete删除单个数组元素: 使用delete运算符不会影响length属性。它也不会影响后续数组元素的索引。数组变得稀疏,这是说删除的项目没有被删除而是变成undefined的一种奇特的方式。constarr〔1,2,3,4,5〕;deletearr〔1〕console。log(arr);〔1,empty,3,4,5〕 实际上没有将元素从数组中删除的原因是delete运算符更多的是释放内存,而不是删除元素。当不再有对该值的引用时,将释放内存。 使用数组filter方法删除匹配的元素: 与splice方法不同,filter创建一个新数组。 filter接收一个回调方法,回调返回true或false。返回true的元素被添加到新的经过筛选的数组中。constarr〔1,2,3,4,5,6,7,8,9,0〕;constfilteredarr。filter((value,index,arr)value5);console。log(filtered);〔6,7,8,9〕console。log(arr);〔1,2,3,4,5,6,7,8,9,0〕 清除或重置数组: 最简单和最快的技术是将数组变量设置为空数组letarr〔1,2,3〕;arr〔〕; 清除数组的一个简单技巧是将其length属性设置为0。letarr〔1,2,3〕;arr。length0; 使用splice方法,不传递第二个参数。这将返回原始元素的一个副本,这对于我们的有些场景可能很方便。也是一种数组复制方法技巧。letarr〔1,2,3〕;arr。splice(0); 使用while循环,这不是一种常用清除数组的方法,但它确实有效,而且可读性强。一些性能测试也显示这是最快的技术。constarr〔1,2,3,4,5,6〕;while(arr。length){arr。pop();}console。log(arr);〔〕12、其他方法 剔除假值:〔1,false,,NaN,0,〔〕,{},123〕。filter(Boolean)〔1,〔〕,{},123〕 是否有一个真值:〔1,false,,NaN,0,〔〕,{},123〕。some(Boolean)true 是否全部都是真值:〔1,false,,NaN,0,〔〕,{},123〕。every(Boolean)false 补零:Array(6)。join(0);00000注意:如果要补5个0,要写6,而不是5。Array(5)。fill(0)。join()00000 数组最大值和最小值:Math。max。apply(null,〔1,2,3,4,5〕)5Math。min。apply(null,〔1,2,3,4,5〕)1 判断回文字符串:conststr1string;conststr2str1。split()。reverse()。join();console。log(str1str2);false 数组模拟队列: 队列先进先出:constarr〔1〕;入队arr。push(2);console。log(入队元素:,arr〔arr。length1〕);2出队console。log(出队元素:,arr。shift());1 获取数组最后一个元素: 像我们平常都是这样来获取:constarr〔1,2,3,4,5〕;console。log(arr〔arr。length1〕);5 感觉很麻烦,不过ES有了提案,未来可以通过arr〔1〕这种方式来获取,Python也有这种风骚的操作: 目前我们可以借助ES6的Proxy对象来实现:constarr1〔1,2,3,4,5〕;functioncreateNegativeArrayProxy(array){if(!Array。isArray(array)){thrownewTypeError(Expectedanarray);}returnnewProxy(array,{get:(target,prop,receiver){propprop;returnReflect。get(target,prop0?target。lengthprop:prop,receiver);;}})}constarr2createNegativeArrayProxy(arr1);console。log(arr1〔1〕)undefinedconsole。log(arr1〔2〕)undefinedconsole。log(arr2〔1〕)5console。log(arr2〔2〕)4 注意:这样方式虽然有趣,但是会引起性能问题,50万次循环下,在Chrome浏览器,代理数组的执行时间大约为正常数组的50倍,在Firefox浏览器大约为20倍。在大量循环情况下,请慎用。无论是面试还是学习,你都应该掌握Proxy用法。