前端系统化学习【JS篇】:(六-4)引用数据类型之Array数组篇

590 阅读27分钟

前言

  • 细阅此文章大概需要 30分钟\color{red}{30分钟}左右
  • 本篇中讲述了:
      1. Array数组的概述
      1. Array数组中常用的方法
        1. 实现数组增删改的方法
        1. 实现数组查询和拼接的方法
        1. 实现数组转为字符串的方法
        1. 检测数组中是否包含某一项的方法
        1. 数组排序或排列的方法
        1. 遍历数组的方法
      1. 【扩展】 ES6中扩展运算符(spread)和剩余运算符(rest)
      1. 数组去重的多种思路
      1. 数组塌陷问题的解决思路
      1. 类数组等可遍历对象转换为数组的方法
      1. 【类数组等可遍历对象】可通过【修改this】来使用【某些数组的方法】
  • 如果有任何问题都可以留言给我,我看到了就会回复,如果我解决不了也可以一起探讨、学习。如果认为有任何错误都还请您不吝赐教,帮我指正,在下万分感谢。希望今后能和大家共同学习、进步。
  • 下一篇会尽快更新,已经写好的文章也会在今后随着理解加深或者加入一些图解而断断续续的进行修改。
  • 如果觉得这篇文章对您有帮助,还请点个赞支持一下,谢谢大家!

数组

数组是特殊的对象数据类型

  1. [属性值]在中括号中设置的是属性值
    var d = {d:'dddd'};
    var e = [1,2,3,4,5];
    var arr = ['a','b',11,{c:'ccc'},d,e];
    console.log(arr);// =>["a", "b", 11, {…}, {…}, Array(5)]
  1. 属性名(索引)数组中的属性名是默认生成的从零开始递增的数字,而且这个数字代表每一项的位置,我们把其称为 “索引”=>从零开始,连续递增,代表每一项位置的数字属性名

    • ========对象[数字索引] :仅数组可用=======
  2. 每个数组中,天生默认包含一个一个属性名,length,用来存储数组的长度

    • 注意: 访问数组的时候与普通对象十分相似
      • 数字属性名只可用中括号
      • 其余的【字符串属性名】可以用.号调用,【也可以用中括号调用,但记住加上引号,作为字符串放在中括号中】
    let ary = [12,'哈哈',true,13];
    console.log(ary.length);//=>4
    console.log(ary[length]);//对于字符串属性名来说,可以用.来访问也可以用中括号['属性名']来访问
    console.log(ary[1]);//对于对象来说,数字属性名用中括号来访问
    //第一项索引为0,最后一项索引为length-1
    console.log(ary[0]);//12
    console.log(ary[ary.length-1]);//13
  • 具有symbol(Symbol.iterator)属性的,声明它是可被迭代的数据结构,可以用ES6中的for of循环

数组中常用的方法

  • 数组中的方法有四个维度来描述

  • 方法的作用和含义
  • 方法的实参(类型和含义)
  • 方法的返回值
  • 原来数组是否会发生改变
  • Array.prototype可以查看数组原型对象上的所有内置方法

【扩展】 ES6中扩展运算符(spread)和剩余运算符(rest)详解

  • ***展开运算符(spread)***用三个点号表示,功能是把数组或类数组、对象的每一项展开
  • ***【剩余运算符(rest)】***运算符也是三个点号,不过其功能与扩展运算符恰好相反,把逗号隔开的值序列组合成一个数组 【返回值是一个数组】
  • 当三个点(...)在等号左边,或者放在形参上。为 剩余运算符(rest)运算符
  • 当三个在等号右边,或者放在实参上,是扩展运算符(spread)
  • 或者说:
  • 放在被赋值一方是剩余运算符(rest)运算符。放在赋值一方是扩展运算符(spread)。

1. 实现数组增删改的方法

  • 这一部分方法都会修改原有的数组
  1. push

    • push向数组末尾增加内容
    • @param【代表参数】
      • 可添加 多个任意类型的参数
    • @return【返回值】
      • 返回 新增后数组的长度
    • 原来的数组被修改
        let ary = [10,20];
        let res = ary.push(30,'AA',{name:'aa'});
        console.log(res,ary);//5,[10,20,30,'AA',{name:'aa'}]
        //可添加多个任意类型的参数
        //返回新增后数组的长度
    
    • 除了push,还可用原生js的方法操作键值对的方式【利用数组长度】来向数组后面添加元素
        //原生js的方法【利用数组长度】
        //向数组末尾追加内容
        let ary = [12,'哈哈',true,13];
        ary[ary.length] = 100;
        console.log(ary);
        //[12, "哈哈", true, 13, 100]
    

  1. unshift

    • unshift向数组开头增加内容
    • @param
      • 可添加多个任意类型的参数
    • @return
      • 返回新增后数组的长度
    • 原来的数组被修改
        let ary = [10,20];
        let res = ary.unshift(30,'AA',{name:'aa'});
        console.log(res,ary);//5,[30,'AA',{name:'aa'}10,20]
        //可添加多个任意类型的参数
        //返回新增后数组的长度
      
      • 除了unshift,还可用原生js的方法【ES6中扩展运算符(spread)】把原有的ary数组克隆一份,来向数组开头追加元素
       // 若不使用unshift方法向数组前面添加元素,而是于原生JS,则可以使用【ES6中扩展运算符(spread)】把原有的ary数组克隆一份
       // 在新数组中创建第一项,余的内容使用原生的ary数组的内容,也算是实现了向开头追加的效果。
        let ary = [10,20];
        ary = [100,...ary];
        console.log(res,ary);//3,[100,10,20]
        
      

  1. shift

    • shift删除数组的第一项
    • @param
    • @return
      • 返回被删除的那一项
    • 原来的数组被修改
        let ary = [10,20,30,40];
        let res = ary.shift();
        console.log(res,ary);//10,[20,30,40]
        //返回被删除的那一项
        //删除数组的第一项
      
    • 除了shift,基于原生js中的DELETE方式,把数组当成普通对象确实可以删除数组的某项元素,但也不会影响数组本身的结构特点【length长度不会跟着修改,本删除的元素变empty】【真实项目中杜绝这样使用删除】
      • 真删除:delete 属性名;(将该键和值一并删除)
      • 假删除:使用操作属性的方法对属性值进行修改null。(属性名还在,属性值为空)
       // 若不使用shift方法删除数组首项元素,而是基于生JS
       // 则可以【使用delete进行真删除和假删除】
       let ary = [10,20,30];
       delete ary = [0];
       console.log(ary);// [20,30,length:3]
    

  1. pop

    • pop删除数组的最后一项
    • @param
    • @return
      • 返回被删除的那一项
    • 原来的数组被修改
        let ary = [10,20,30,40];
        let res = ary.pop();
        console.log(res,ary);//40,[10,20,30]
        //返回被删除的那一项
        //删除数组的最后一项
      
    • 除了pop,基于原生js,【可以使用数组长度减一,默认删掉最后一项】
        let ary = [10,20,30,40];
        ary.length--;
        console.log(ary);//[10,20,30,length:3]
      

  1. splice【增加、删除、修改】\color{red}{【增加、删除、修改】}

    • splice增加、删除、修改数组的任意一项
    • @param
      • n,m【都是数字】 从索引n开始【包括第n项】除m个元素【m不写,是删除到末尾】
    • @return
      • 把删除的部分用【新数组】存储并返回
    • 原来的数组被修改

    • 删除

           let ary = [10,20,30,40,50,60,70,80,90];
           let res = ary.splice(2,4);
           console.log(res,ary);//[30,40,50,60][10,20,70,80,90]
           //把删除的部分用新数组存储并返回
           //删除从第n项开始的m个元素,包含第n项
      
      • 【数组.splice(0);】该操作为清空数组
        • 基于这种方法可以把【原数组清空】并以【新数组储存原数组】并返回(有点类似数组的克隆:把原来数组克隆一份模一样的给新数组)
        //清空数组
        let ary = [10,20,30,40,50,60,70,80,90];
        let res = ary.splice(0);
        console.log(res,ary);//[][10,20,30,40,5060,70,80,90]
        
      • 删除最后一项
        //删除最后一项
        let ary = [10,20,30,40,50,60,70,80,90];
        ary.splice(ary.length-1);
        //第二个参数不写就是删到末尾
        console.log(ary);//[10,20,30,40,50,60,70,80]
        
      • 删除第一项
        //删除第一项
        let ary = [10,20,30,40,50,60,70,80,90];
        ary.splice(0,1);
        console.log(ary);//[20,30,40,50,60,70,80,90]
        
      • 基于原生js,【可以使用数组长度减一,默认删最后一项】
      let ary = [10,20,30,40];
      ary.length--;
      console.log(ary);//[10,20,30,length:3]
      

    • 修改

      • @param
        • n,m,x【都是数字】 从索引n开始【包括第项】删除m个元素【m不写,是删除到末尾】,然后【用x占用被删除的部分】
        • x可写多个
        let ary = [10,20,30,40,50];
        let res = ary.splice(1,2,'aaaa','hahaha');
        console.log(res,ary);//[20,30][10,'aaaa''hahaha',40,50]
        //把删除的部分用新数组存储并返回
        

    • 增加

      • @param
        • n,0,x【都是数字】 从索引n开始【一个不删,然后把x放到索引n的前面】
        • x可写多个,且根据n的不同,位置很随意
        let ary = [10,20,30,40,50];
        let res = ary.splice(3,0,'aaaa','hahaha');
        //在索引3的元素前面增加元素
        console.log(res,ary);//[][10,20,30'aaaa''hahaha',40,50]
        

2. 实现数组查询和拼接的方法

  • 这一部分方法都不会修改原有的数组
  1. slice【数组的查询】

    • slice:实现数组的查询
    • @param
      • n,m【都是数字】 从索引n开始【包括第n项】到索引为m的元素【不包含m这一项】
      • 若参数【不写】或者【只写一个0】,相当于【数组的克隆】,此种方式称为浅克隆
    • @return
      • 把找到的内容以一个新数组的形式返回
    • 原来的数组不会被被修改
            let ary = [10,20,30,40,50];
            let res = ary.slice(1,3);
            console.log(res,ary);//[20,30][10,20,30,40,50]
            //从索引n开始【包括第n项】找到索引为m的元【不包含m这一项】
            //把找到的内容以一个新数组的形式返回
            let res1 = ary.slice(0);//【数组的克隆】
            //res1 = [10,20,30,40,50]
            let res2 = ary.slice();//【数组的克隆】
            //res2 = [10,20,30,40,50]
    
        /* 
            思考:
                1. 如果n/m为负数会发生什么?
                2. 如果n>m了会发生什么?
                3. 如果是小数会发生什么?
                4. 如果是非效数字会发生什么?
                5. 如果m或者n的值比最大索引都大会发生什么?  
         */
    
    1. 如果n/m为负数会发生什么? 1
    2. 如果n>m了会生什么? 2
    3. 如果是小数会发生什么? 3
    4. 如果是非效数字会发生什么? 4
    5. 如果m或者n的值比最大索引都大会发生什么? 5

  1. concat【数组的拼接】

    • concat
      1. 将两个数组拼接成一个数组
      2. 【不仅是数组还可将其他类型值拼接到数组中
    • @param
      • 多个任意类型值
    • @return
      • 返回一个拼接后的数组
    • 原来的数组不会被被修改
            let ary1 = [10,20,30];
            let ary2 = [40,50,60];
            let res = ary1.concat('aaa');
            console.log(res);//[10,20,30,'aaa']
            //================================
            let res1 = ary1.concat();
            console.log(res1);//[10,20,30]
            //但是注意:res1===ary1 => false
            //此时只是模拟出数组克隆的效果,但是真实情况中数组克隆并不会使用该方法,因为【其本意只拼接】
            //================================
            let res2 = ary1.concat('aaa',ary2);
            console.log(res2);//[10,20,30,'aaa',40,5060]
          
    

3. 实现数组转为字符串的方法

  • 这一部分方法都不会修改原有的数组
  1. toString【数组转化为字符串】

    • toString【数组转化为字符串】
    • @param
    • @return
      • 返回转换后的字符串,【默认分隔符是每一项用逗号分隔】
      • 空数组转换为空字符串
      • 原来的数组不会被被修改
         let ary1 = [10,20,30];
         let res = ary1.toString();
         console.log(res);//'10,20,30'
         console.log([].toString());//''
         console.log([12].toString());//'12'
      

  2. join【数组转化为字符串】

    • join【数组转化为字符串】

    • @param

      • 指定的分隔符【字符串格式的】【空字符串为没有分隔符】
    • @return

      • 返回转换后的字符串,【若不指定默认分隔符是逗号】,空数组转换为空字符串
    • 原来的数组不会被被修改

        let ary1 = [10,20,30];
        let res = ary1.join('');
        console.log(res);//'102030'
        res = ary1.join();
        console.log(res);//'10,20,30'
        res = ary1.join(' ');
        console.log(res);//'10 20 30'
        res = ary1.join('|');
        console.log(res);//'10|20|30'
        //===================================
        res = ary1.join('+');
        console.log(res);//'10+20+30'
        //===================================
        console.log(eval(res));//60
        //eval是一个内置函数,作用是把字符串变为JS表达式执行
        //===================================
      

4. 检测数组中是否包含某一项的方法

  • 这一部分方法都不会修改原有的数组
  1. indexof/lastIndexOf【检测数组中是否包含某一项】

    • indexof/lastIndexOf检测当前项在数组中【第一次】或【最后一次】出现位置的【索引值】【在IE6-8中不兼容】
    • @param
      • 要检索的这一项内容
    • @return
      • 返回这一项出现的位置索引值(数字),如果数组中没有这一项,返回的结果是-1
    • 原来的数组不会被被修改
            let ary = [10,20,30,10,20,30];
            console.log(indexOf(20));//1
            console.log(lastIndexOf(20));//4
            //向验证数组中是否包含某一内容,若等于-1则一定不包含
            //===================================
            if(ary.indexOf('aaa')===-1){
               console.log('不包含');
            }
            //===================================
    

  2. includes【ES6新提供】【检测数组中是否包含某一项】

    • includes检测当前项在数组中是否存在【在IE6-8中不兼容】
    • @param
      • 要检索的这一项内容
    • @return
      • 若存在该内容,则返回true
      • 若不存在该内容,则返回false
    • 原来的数组不会被被修改
           let ary = [10,20,30,10,20,30];
           //===================================
           if(ary.includes('aaa')){
              //若存在该内容,则返回true
              //若不存在该内容,则返回false
            }
            //===================================
    

5. 数组排序或排列的方法

  • 这一部分方法会修改原有的数组
  1. reverse【把数组倒序排列】

    • reverse:把数组倒序排列
    • @param
    • @return
      • 返回排列后的新数组,且原数组也会被改变
    • 原来的数组被修改
            let ary = [10,20,30,10,20,30];
            console.log(ary.reverse());//[30,20,10,30,20,10]
    

  2. sort【数组排列】

    • sort:实现数组排列由小到大排序
    • @param
      • 可以没有,也可以是个函数
      • 若不传递参数,则无法处理10以上排序【因为默认是按照每一项的第一个字符来排序,不是我们想要的效果】
      • 想要实现多位数正常排序,需要给SORT传递一个函数,函数中【return a-b实现升序】【return b-a实现降序】【原因需了解冒泡排序】
    • @return
      • 返回排序后的新数组,且原数组也会被改变
    • 原来的数组被修改
            let ary = [3,2,1];
            console.log(ary.sort());//[1,2,3]
            //===========================
            let ary1 = [12,15,9,22,10,28]
            console.log(ary1.sort());//[10,12,15,22,28,9]
            //===========================
            //===========================
            //传递参数修改排序方式【从小到大】
            let ary1 = [12,15,9,22,10,28]
            //a和b是相邻两项
            ary1.sort((a,b)=>return a-b);
            console.log(ary1.);//[9,10,12,15,22,28]
            //===========================
            //===========================
            //传递参数修改排序方式【从大到小】
            let ary1 = [12,15,9,22,10,28]
            //a和b是相邻两项
            ary1.sort((a,b)=>return b-a);
            console.log(ary1.);//[28,22,15,12,10,9]
             //===========================
    

6. 遍历数组每一项的方法

  • 这一部分方法不会修改原有的数组
  1. foreach【遍历数组每一项】

    • foreach:遍历数组每一项
    • @param
      • 参数是一个回调函数, 【数组当中有多少项这个回调函数就会被执行多少次】
      • 每一次执行这个该回调函数:【item:是当前数组中要操作的这一项】【index:是当前数组中要操作的这一项的索引】
        • 【ary.forEach((item,index)=>{})】
    • @return
    • 原来的数组不会被修改
            //foreach方法来循环遍历数组
            //真实项目中大多使用foreach
            let ary1 = [12,15,9,22,10,28]
            ary.forEach((item,index)=>console.log('索引:'+index+'内容'+item));
    
    • 【若不用数组内置方法,想要遍历每一项,则可以使用原生JS中的循环利用索引来实现】
        //若不用数组内置方法,想要遍历每一项,则可以使用原生JS中的循环利用索引来实现】
            let ary1 = [12,15,9,22,10,28]
            for(let i = 0;i < ary.length;i++){
                console.log('索引:'+i+'内容'+ary[i]]);
            }
    

  2. map【遍历并操作数组每一项】

    • map:创建一个新数组,其结果是原数组中的每个元素都调用一个提供的函数后返回的结果。
    • @param
      • function(currentValue,index,arr) 【数组中的每个元素都会执行这个函数】
        • 函数参数
          1. currentValue 必须 当前元素值
          2. index 可选 当前元素的索引值
          3. arr 可选 当前元素属于的数组对象
    • @return
      • 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
    • 不会改变原始数组
    • 不会对空数组进行检测
          var arr = [12,21,39,6];
    	  const map1 = arr.map(cur => cur * 2);
          const map2 = arr.map(cur => {
          	if(cur>20){
            	return cur * 2;
                }
            return cur;
           });
           console.log(map1);// =>[24, 42, 78, 12]
           console.log(map2);// =>[12, 42, 78, 6]
    

  1. filter【遍历并过滤数组每一项】

    • filter:创建一个新数组,新数组中的元素是通过检查指定数组中【符合条件】的所有元素。
      • (原数组的每个元素传入回调函数中,回调函数中若返回值为true,这个元素保存到新数组中;若返回值为false,则该元素不保存到新数组中;原数组不发生改变。)
    • @param
      • function(currentValue,index,arr) 【数组中的每个元素都会执行这个函数】
        • 函数参数
          1. currentValue 必须 当前元素值
          2. index 可选 当前元素的索引值
          3. arr 可选 当前元素属于的数组对象
    • @return
      • 返回一个新数组
    • 不会对空数组进行检测
    • 不会改变原始数组
          let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
          let res = arr.filter(cur => {
            return cur > 5;
          });
          console.log(res);  // [6, 7, 8, 9]
    

  1. find【遍历数组每一项,返回满足要求的第一项】

    • find:返回数组中满足提供的测试函数的第一个元素的值,之后的值不会再调用执行函数。如果没有符合条件的元素则返回 undefined。
    • @param
      • function(currentValue,index,arr) 【数组中的每个元素都会执行这个函数,直到不再检测】
        • 函数参数
          1. currentValue 必须 当前元素值
          2. index 可选 当前元素的索引值
          3. arr 可选 当前元素属于的数组对象
    • @return
      • 返回一个元素或者undefined
    • 不会对空数组进行检测
    • 不会改变原始数组
            let arr = [1,10,12,24,6,8,12,5,21];
            function fn(cur){
            	return cur > 12;
            }
            function fn1(cur){
            	return cur < 1;
            }
            let res = arr.find(fn);
            let res1 = arr.find(fn1);
            
            console.log(res); // =>24
            console.log(res1);// =>undefined
            
    

  1. reduce【累加数组的每一项】【累计】【实现compose】

    • 语法arr.reduce(callback,[initialValue])
    • reduce:reduce() 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
    • @param
      • callback 回调函数
        • callback:函数中包含四个参数
          1. previousValue【n】 (上一次调用回调返回的值,或者是reduce提供的初始值(initialValue)【reduce的第二个参数】)
          2. currentValue(数组中当前被处理的元素)
          3. index (当前元素在数组中的索引)
          4. array(调用的数组)
      • initialValue 作为第一次调用 callback 的第一个参数
    • @return
      • [NUMBER]
        let arr = [10, 20, 30, 40];
        let res = arr.reduce((n,item)=>{
            //第一次触发回调函数执行,n是第一项,item是第二项
            //第二次触发回调函数执行,n是上一次回调函数返回结果【第一项与第二项的和】,item是第三项【下一项】
            //第N次触发回调函数执行,n是第N-1次回调函数返回结果,item是下一项
            console.log(n,item);
            //return的信息会作为下一次回调函数执行时n的值
            return n+item;
        });
          
    

  1. some【遍历数组每一项,判断是否含有满足条件的元素】

    • some:用于检测数组中是否含有满足指定条件的元素
      • 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
      • 如果没有满足条件的元素,则返回false。
    • @param
      • function(currentValue,index,arr) 【数组中的每个元素都会执行这个函数,直到不再检测】
        • 函数参数
          1. currentValue 必须 当前元素值
          2. index 可选 当前元素的索引值
          3. arr 可选 当前元素属于的数组对象
    • @return
      • 返回一个布尔值
    • 不会对空数组进行检测
    • 不会改变原始数组
          	let arr = [1,10,12,24,6,8,12,5,21];
            function fn(cur){
            	return cur > 12;
            }
            function fn1(cur){
            	return cur < 1;
            }
            let res = arr.some(fn);
            let res1 = arr.some(fn1);
            
            console.log(res); // =>true
            console.log(res1);// =>false   
    

  1. every【遍历数组每一项,判断是否所有元素都满足条件】

    • every:用于检测数组是否所有元素都符合指定条件
      • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测
      • 如果所有元素都满足条件,则返回 true
    • @param
      • function(currentValue,index,arr) 【数组中的每个元素都会执行这个函数,直到不再检测】
        • 函数参数
          1. currentValue 必须 当前元素值
          2. index 可选 当前元素的索引值
          3. arr 可选 当前元素属于的数组对象
    • @return
      • 返回一个布尔值
    • 不会对空数组进行检测
    • 不会改变原始数组
    		let arr = [1,10,12,24,6,8,12,5,21];
            function fn(cur){
            	return cur >= 1;
            }
            function fn1(cur){
            	return cur < 12;
            }
            let res = arr.every(fn);
            let res1 = arr.every(fn1);
            
            console.log(res); // =>true
            console.log(res1);// =>false   
            
    

数组去重

  • 几种思路:

  • 1.【低级方法】 创建空数组,利用循环和判断来实现。

    • 循环获取原有数组中的每一项,添加到空数组中,每次添加的时候都要验证新数组是否已经包含这一项,如果包含则不放。
        let ary = [1,2,3,4,2,3,1,2,2,4,1,3];
        let newAry = [];
        for (let i = 0;i< ary.length;i++){
            let item = ary[i];
            if(newAry.includes(item)){
                continue;
            }
            newAry.push(item);
        }
        console.log(newAry); // =>[1,2,3,4]
    
    • 改良使用foreach来实现
        let ary = [1,2,3,4,2,3,1,2,2,4,1,3];
        let newAry = [];
        ary.forEach(item=>{
            if(newAry.includes(item)) return;
            //判断体内只有一行代码,大括号可省去
            //IE6~8不兼容includes
            newAry.push(item);
        });
        console.log(newAry);// =>[1,2,3,4]
    

  • 2.【低级方法】直接操作原数组,利用循环和判断来实现。【兼容性好】【数组塌陷问题】

    • 循环原数组中的每一项,每一次拿出的值都和它后面的值依次进行比较
    • 比较过程中只要遇到和自己相同的,就把这一项从数组中删除【使用splice方法】,然后再开始下一项,接着向后循环排除。
    • 这样就可以不用includes / indexOf(这样保证兼容性)
        var ary = [1,2,3,4,2,3,1,2,2,4,1,3];
        for (var i = 0;i< ary.length;i++){
            var item = ary[i];
           /*  var itemBcak = ary.slice(i+1);
            //创建新数组itemBcak,用来存储当前项后面的所有项 */
            for (var j = i+1;j< ary.length;j++){
                var compare = ary[j];
                if(item === compare){
                    //删除J这一项
                    //但是只这样的方式并不能实现,因为会导致【数组塌陷问题】
                    ary.splice(j,1);
                    j--;//【解决数组塌陷】在判断到相同元素并对数组进行操作之后,对循环标识进行--操作,以保证索引的正常位置,以解决数组塌陷的问题。
                }
            }
         }
        console.log(ary); // =>[1, 2, 3, 4]
    

    • 【数组塌陷问题】

      • 当利用循环嵌套进行对比,并移除数组当中重复元素的时候, 由于是在原数组上进行修改,所以会导致数组塌陷的问题
      • 【在数组中每删掉一项,则后这一项后面每一项的索引都要提前一位,所以会导致按照索引来操作并不能正确的删除元素】
           var ary = [10,20,30];
           for(var i = 0; i<ary.length;i++){
               ary.splice(i,1);
               //修改原数组,会导致原来数组发生改变
           }
           //原数组
           //[10,20,30]
           // ↑  ↑  ↑
           // 0  1  2
           
           //则第一轮循环时
           // i = 0 i<3 i++
           //ary.splice(0,1); 索引为0的那项会被删除 但是原数组发生了改变
           //第一轮循环过后的数组
           //[20,30]
           // ↑  ↑  
           // 0  1 
           //则第二轮循环时
           // i = 1 i<3 i++
           //ary.splice(1,1); 
           //想要的效果是20被删除,但是由于数组塌陷导致索引变动,最后索引为1的那项【30】被删除
               
      
      • 解决方法:【在对原数组进行操作后,将循环标记--,再进入下一循环】【若没有操作,其还是会正常的进行累加】

  • 3.【高阶方法】利用循环、判断、对象来实现。【减少一层循环】【兼容性好】

    • 相较于循环套循环,此种方式基于对象处理,能够减少一层循环,进而优化性能,同时兼容性也不错
    • 缺陷:数组中对象类型的值无法处理
        let ary = [1,2,3,4,2,3,1,2,2,4,1,3];
        //1.创建一个空对象
        let obj = {};
        //2.循环数组中的每一项,把每一项向对象中存储=>item:item
        for(let i = 0;i<ary.length;i++){
            let item = ary[i];
            //3.每次存储之前进行判断:验证obj中是否存在这一项
            if(obj[item]!==undefined){//说明此项已存在
                ary.splice(i,1);
                i--;
                continue;
            }
            //4.若对象中不存在该项,则添加到对象
            obj[item]=item;
            //注意obj[item]和obj['item']的区别:
            //obj[item]代表obj中【属性名是【对象item中存储的值】】
            //obj['item']代表obj中的【属性名为item的属性名】
        }
        console.log(ary);// =>[1, 2, 3, 4]
    
    • 但是基于splice实现删除,实质上性能还是不好,因为当前项被删后,后面的每一项索引都要提前一位,若后面内容过多,一定会影响性能。

  • 4.【高阶方法】利用循环、判断、对象来实现。【减少一层循环】【兼容性好】【不基于splice删除,避免数组塌陷,避免性能浪费】【较为优秀的方案】

    • 遇到重复项,就把数组的最后一项赋值给当前项,然后把最后一项删除 避免了数组塌陷问题
    • 但是仍需给循环标记--,因为最后一项拿过来之后,还是要再比对一下
    • 缺陷:数组中对象类型的值无法处理
        let ary = [1,2,3,4,2,3,1,2,2,4,1,3];
        //1.创建一个空对象
        let obj = {};
        //2.循环数组中的每一项,把每一项向对象中存储=>item:item
        for(let i = 0;i<ary.length;i++){
            let item = ary[i];
            //3.每次存储之前进行判断:验证obj中是否存在这一项
            if(obj[item]!==undefined){//说明此项已存在
                ary[i]=ary[ary.length-1];//用最后一项替换当前项
                ary.length--;//删除最后一项(不能写为ary.length-1)
                i--;//循环标记减一,以对替换过来的那项再重新进行比对
                continue;
            }
            //4.若对象中不存在该项,则添加到对象
            obj[item]=item;
            //注意obj[item]和obj['item']的区别:
            //obj[item]代表obj中【属性名是【对象item中存储的值】】
            //obj['item']代表obj中的【属性名为item的属性名】
        }
        console.log(ary);// =>[1, 2, 3, 4]
    

  • 封装为对象【需要数组去重时直接调用】:

        /* 
         * unique:实现数组去重的方法
         *   @params
         *      Ary [Array] 要去重的数组【←中括号代表参数的类型】
         *   @return
         *       [Array] 去重后的数组
         *
         * by lsw on 20200730
         */
        
        function unique(Ary){
            //1.创建一个空对象
            let obj = {};
            //2.循环数组中的每一项,把每一项向对象中存储=>item:item
            for(let i = 0;i<Ary.length;i++){
                let item = Ary[i];
                //3.每次存储之前进行判断:验证obj中是否存在这一项
                if(obj[item]!==undefined){//说明此项已存在
                    Ary[i]=Ary[Ary.length-1];//用最后一项替换当前项
                    Ary.length--;//删除最后一项
                    i--;//循环标记减一,以对替换过来的那项再重新进行比对
                    continue;
                }
                //4.若对象中不存在该项,则添加到对象
                obj[item]=item;
                //注意obj[item]和obj['item']的区别:
                //obj[item]代表obj中【属性名是【对象item中存储的值】】
                //obj['item']代表obj中的【属性名为item的属性名】
            }
            return Ary;
        }
        //====================使用========================
            let aa = [1,2,3,4,2,3,1,2,2,4,1,3];
            aa = unique(aa);//调用封装好的方法去重
            aa.sort((a,b)=>a-b);//排序
            console.log(aa);
    

  • 5.【正则表达式方法】正则表达式。【奇怪的方法增加了】

         let ary = [12,12,14,15,16,23,23,25,25];
         //1.先排序
         ary.sort((a,b)=>a-b);
         //2.数组转为字符串
         let str = ary.join('@')+'@';
         //3.用正则筛选
         let reg = /(\d+@)\1*/g;//1+:代表1到多位 1*:代表0到多位
         //['12@12@','14@','15@','16@','23@23@','25@25@']
         ary = [];
         str.replace(reg,(n,m)=>{
             m = Number(m.slice(0,m.length-1));//只取每一项的@前面的数字,并转为数字类型
             ary.push(m);//写入数组,去重完毕
         });
         console.log(ary);
    

  • 6.【ES6】基于Set(对应的Map)。【奇怪的方法增加了】

    • 基于new Set 实现数组去重.....
             let ary = [12,12,14,15,16,23,23,25,25];
             ary = [...new Set(ary)];
             //...:代表把后面表达式中的每一项拿出来,赋值给这个新数组,【剩余/展开表达式】
             console.log(ary);
    

转换为数组的方法【类数组通过修改this使用某些数组的方法】

  • 1. 直接使用ES6中的剩余运算符获取参数集合,返回值本身就是一个数组

		function toArray(...args){
             return args;
        } 

  • 2. 直接使用ES6中的展开运算符获取类数组、对象,并且展开每一项(此处是用arguments类数组展开)

    • arguments是实参集合,获取的结果是一个类数组(是个对象)
    • 箭头函数中没有arguments。不是Array的实例,无法使用数组的方法
		function toArray(){
             return [...arguments];
        } 

  • 3. 直接使用ES6中的Array.from将类数组转化为数组(此处是用arguments类数组)

    • arguments是实参集合,获取的结果是一个类数组(是个对象)
    • 箭头函数中没有arguments。不是Array的实例,无法使用数组的方法
		function toArray(){
             return Array.from(arguments);
        } 

  • 4.【笨办法】循环遍历arguments,逐项添加到新数组中

		function toArray(){
             for(let i = 0;i<=arguments.length;i++){
                 let arr = [];
                 arr.push(arguments[i]);
             }
             return arr;
        } 

  • 5. 使用Array.slice方法,通过改变call指向,来实现将类数组转化为数组【数组浅克隆】

    • 实际上,slice的源码也是通过循环遍历来实现的,只不过里面用的是this实现的。
    • 如果我们通过改变当中的this指向类数组,就可以实现将【类数组】中的【指定范围元素克隆为一个新数组】
    • 从而通过【数组的克隆】实现【类数组到数组】的转化
    • 原理:只要把slice执行,让方法中的this变为arguments,这样就可以实现把类数组argument转化为数组
    • 前提
      • 操作数组的代码(内置方法中的代码)也要适配改变的this(类数组)
      • 对象则需要进一步修改,因为类数组和数组类似,都有索引和长度
    1. slice执行 Array.prototype.slice() 或者 [].slice()【内置类原型调用,或者实例直接调用】
    • slice的源码
      //=========
      //slice的源码
      //=========
      Array.prototype.slice = function slice(){
      for(let i = 0;i<=this.length;i++){
          let arr = [];
          arr.push(this[i]);
         }
          return arr;
         } 
    
    1. 修改了this为arguments的slice执行,Array.prototype.slice.call(arguments) 或者 [].slice.call(arguments)
     //======
     //当修改this作为类数组使用时
     //======
          return [].slice.call(arguments)
    

由此可推断出【如foreach等方法也可以通过这种方式,也可以让类数组来使用】

//如
[].forEach.call(arguments,item=>{
     console.log(item);
    });