closertb / closertb.github.io

浏览issue 或 我的网站,即可查看我的所有博客
https://closertb.site
32 stars 0 forks source link

用好JS 原生API系列之数组 #15

Open closertb opened 4 years ago

closertb commented 4 years ago

写于:2017-11-30

前言

最近工作做数据交互展示,常和数据打交道,而随之而来的就是遇见后端传来的各种各样的数组,我需要用各式各样的方法来变换这些数据,来最好的展示这些数据;很多东西久了没用就容易忘,自己也是边查边用,这篇文章算是自己这一周学习的知识的总结。当然你也可以打看MSDN查看更标准的叙述

数组原生的API

那些需要知道的特性:

  1. 数组在JS中是对象的一种,所以数组也是引用类型,所以操作时要小心,时刻记住你操作的不是一个普通类型;
  2. 每个数组都自带一个length属性,这个属性很特别,可读也可写。JS函数的arguments因为也拥有length属性,所以其被称为类数组对象。 这里不会提到所有的API,因为真的太多了。我只提我最近常用的,还有被人们常说的增删查改。

    API之增删查改

    我以前听说增删查改(CRUD),是sql语言。但有一次参加面试,面试官问:说说你知道的JS数组增删查改,那数组的增删查改是那些呢?说真的,当时我一脸懵逼,我还以为数组还能用sql语句操作

    • 增加:push,unshift,还有通常我们常用的arr[arr.length] = newChild; 首先我们有一个数组arr= [1,2,3,4,5,6] push被称为栈操作,即栈顶入,栈顶出,所以当我们采用arr.push(7),得到的结果是arr= [1,2,3,4,5,6,7],其相对于arr[arr.length] = 7; unshift和push相反,即栈底入,所以当我们做和上面相似的操作,即arr.push(7),得到的结果是arr= [7,1,2,3,4,5,6] 至于arr.push(3.5)和arr.unshift(3,5)答案是多少,你可以自己亲自尝试一下;
    • 删除:pop(对应push),arr.pop()操作后,arr是arr= [1,2,3,4,5];shift(对应unshift),arr.shift()操作后,arr= [2,3,4,5,6];操作length,当我们arr.length = 4操作后,得到 arr =[1,2,3,4]; 这里提一句,使用delete arr[arr.length-1],并不能删除最后一个元素,而只是将最后一个元素的赋值去除,值为undefined,且其length未变.
    • :很多时候我们都采用for循环遍历加比较,来查找某个元素在数组中的索引,但其实js是支持indexOf方法的,当我们进行arr.indexOf(3),其会返回结果2,进行arr.indexOf(9),其返回的结果就是-1,和字符串的indexOf方法何其相似,所以也有相对应的lastIndexOf()方法;
    • :改在数组操作中很常用,也很直接,这个就不长述了。 这里说三个重要的方法concat,slice和splice,concat主要做数组拼接(我经常用它做数组的深拷贝);slice主要做数组截取,而splice几乎能完成上述所有的CUD操作,之所以要把他们分开提,是因为这两个方法操作较复杂,其cancat与slice并不是对数组本身的操作,而是会产生一个新的Array数组,被操作的数组并没有改变;而splice方法,是直接对数组进行操作,仅当参数删除或替换元素操作时,会返回一个新的数组,其包含的元素就是返回的元素。
    • concat方法:数组的拼接,array.concat([item1[, item2[, . . . [, itemN]]]]) ,array指被拼接的对象数组,item为数组。但我用的最多的就是数组的深拷贝,看实例:
      var hege = ["Cecilie", "Lone"];
      var stale = ["Emil", "Tobias", "Linus"];
      var kai = ["Robin"];
      var children = hege.concat(stale,kai);
      console.log(children)  //打印["Cecilie", "Lone","Emil", "Tobias", "Linus","Robin"]
      var deepArr = stale.concat([]);//数组深拷贝;
      deepArr.push('Denzel'); 
      console.log(deepArr); //打印["Emil","Tobias", "Linus","Denzel"]
      console.log(stale); //打印["Emil", "Tobias", "Linus"];
    • slice方法:arrayObj.slice(start, [end]),从方法的描述可知,其可接受两个参数,start即截取开始的位置,end截取结束的位置,参数可选,如果没有,截取的位置是直到数组末尾,但需要注意的是,start位置的元素是被截取的,而end位置的元素是不包含的,只截取该元素前一个元素。
      var arr= [1,2,3,4,5,6]
      var newArr = arr.slice(0,arr.length);
      console.log(newArr)  //打印 [1,2,3,4,5,6]
      newArr = arr.slice(3,5);
      console.log(newArr)  //打印 [4,5]

      其实前面都不复杂,复杂的是参数为负的时候,规则是这样的(来源于MSDN):如果 start 为负,则将其视为 length + start,其中 length 为数组的长度。如果 end 为负,则将其视为 length + end,其中 length 为数组的长度。如果省略 end,则将一直提取到 arrayObj 的结尾。如果 end 出现在 start 之前,则不会将任何元素复制到新数组中。

      var arr= [1,2,3,4,5,6]
      var newArr = arr.slice(0,-1);
      console.log(newArr)  //打印 [1,2,3,4,5]
      newArr = arr.slice(-3,-1);
      console.log(newArr)  //打印 [4,5]
    • splice方法:arrayObj.splice(start, deleteCount, [item1[, item2[, . . . [,itemN]]]]),相比slice,其复杂太多,所以几乎能完成所有的cud操作,但与其不同的是,上面的方法都只能在数组的头和尾上进行操作,splice能完成任意位置的cud操作;其arrayObj参数为必需,且必须是一个 Array 对象;start参数必需,指数组中移除元素操作的起点,从 0 开始,deleteCount参数必需,指要移除的元素的个数,上面提到的返回数组,其数组元素的个数与deleteCount相等,当deleteCount=0时,其返回一个空数组;item1, item2,. . ., itemN可选,指插入数组中代替已移除元素的元素。直接看实例吧:
      
      let arr =[1,2,3,4,5,6]  //以下三步是独立操作,非连续操作。偷了个小懒
      arr.splice(2, 2, '11','12'); //这个操作删除了3,4,并在其位置上添加了11,12,相当于改,其结果[1, 2, "11", "12", 5, 6]

arr.splice(2,0, '11','12');//这个操作删除了0个元素,添加了11,12,相当于增,其结果[1, 2, "11", "12",3,4,5,6];

arr.splice(2,2, );//这个操作删除了2个元素,添加了0个元素,相当于删,其结果[1, 2, "11", "12",3,4,5,6],相当于增加;

splice方法的start参数也支持负数,其会自动类加length,直到为正。    
### API之重排序
说道数据处理,也许你利马会想到排序,什么冒泡,差值,希尔,快速排序算法。而JS提供了reverse()数组反转和sort()排序来对数组进行重排序。
- **数组反转**reverse():和其名字描述的一致,用于数组反转,需要注意的是,其是对数组本省的操作,并不会产生新数组;很简单,看个示例就明白了:
```javascript
var color =['a','b','e','d','c','f'];
color.reverse();
console.log(color);//打印[f,c,d,e,b,a]

对于上面5个方法,都有类似的回调参数(item,index,array),试着从一个例子来了解他们,一个简单的例子显得有些苍白。假如现在我们有这样一个需求,已知某个四川省某个景区今日接待旅客总人数10000人,然后从购票信息获取到前十名的省份和人数,我们想计算这些省份每个所占比例,并把他们的人数用一个数组单独保存下来,用来找最大值,最小值,我们试着不用for循环来解决这个问题;

var totle = 10000;
var data = [
  {name:'四川省',num:3000},
  {name:'重庆市',num:500},
  {name:'江西省',num:900},
  {name:'湖南省',num:600},
  {name:'陕西省',num:800},
  {name:'河北省',num:300},
  {name:'湖北省',num:400},
  {name:'北京市',num:600},
  {name:'云南省',num:400},
  {name:'湖南省',num:300}
];
var tempArr = [];
data = data.map(function (item) {
  tempArr.push(item.num);
  item.percent = item.num/totle;
  return item;
})
console.log(tempArr); //[3000, 500, 900, 600, 800, 300, 400, 600, 400, 300]
console.log(data); //0:{name: "四川省", num: 3000, percent: 0.3} 1:{name: "重庆市", num: 500, percent: 0.05}......

短短四行代码就完成了这两个需求,如果我们只想要第2名到第9名的数据(即去掉一个最高分,去掉一个最低分)拿来做分析,所以根绝前面抽离出来的数组,我们再借助sort()和filter()来完成这个需求:

tempArr.sort(function (v1,v2) { // 降序排列
    return v2-v1;
});
var max = tempArr.shift(),min = tempArr.pop();
data=data.filter(function (item) {  //这个方法并没有完全达到需求,这里只是演示filter的用法,你可以试着优化这个函数,来完成这个需求
    return (item.num!==max)&&(item.num!==min)
})
console.log(tempArr);  //体会一下shift和pop[900, 800, 600, 600, 500, 400, 400, 300]
console.log(data);   //0:{name: "重庆市", num: 500, percent: 0.05} 1:{name: "江西省", num: 900, percent: 0.09}打印出来,结果只剩下7个了,因为最小值出现了两次。

通过上面两个例子,也许你应该已经体会到了这些原生数组API的作用了,他们在数据处理中,优势非常大,但也不能说,以后就可以完全不依赖ofr循环了,还是很难,上面五个方法有一个通病,就是无法中止遍历,即在循环中break,break一些遍历查找中,还是相当省时,这也是为啥有时我们还是需要for循环来做一些操作. 至于some,every,foreach,你可以自己动手感受一下。 别总是沉溺于已会的那点知识,别总依赖框架,插件。用好原生API,你的代码将显得干净,有趣。