10

一.前言

因为在工作当中,经常使用到js的数组,而其中对数组方法的使用也是很频繁的,所以总是会有弄混或者概念不够清晰的状况,所以,写下这篇文章整理一番,本文有对几乎所有数组的方法的介绍,此外还有非常实用的一些数组操作比如乱序去重和斐波那契数列求值等等,总之干货满满~~

二.JS中的Array方法

1.检测数组

   //instanceof 测试某个对象是否由某个指定的构造器创建

     [1,2,3] instanceof Array //true
      "1" instanceof Array // false
   
   //比instanceof更可靠 Array.isArray

    Array.isArray([1,2,3]); //true

   //Object对象的toString()方法,可以返回所创建对象的内部类名

    Object.prototype.toString.call([1,2,3]); //"[object Array]"

    Object.prototype.toString.call("a"); //"[object String]"

2.转换方法

   const test = [1,2,3];

   test.toString(); //"1,2,3"

   test.valueOf(); //[1,2,3]

   //toLocaleString大部分为返回与toString相同的结果,区别之处在于会调用每一项的toLocaleString()方法
   test.toLocaleString([1,2,3]); //[1,2,3];

   const testStr = test.join("|"); //"1|2|3"
   testStr.split("|"); // [1,2,3];

3.栈方法(push和pop 尾部操作)

  const test = new Array();
  const count= test.push("a","b");
  //count为操作完后的数组长度
  console.log(count); //2
  
  const count1 = test.push("c");
  console.log(count1); //3
  
  const item = test.pop();
  console.log(item); //"c"
  

4.队列方法(shift和unshift 头部操作)

   const test = [1,2,3];
   const item = test.shift();
   console.log(item); //1
   console.log(test); //[2,3];
   

5.重排序方法

  const test = [1,2,3];
  const test1 = test.reverse(); // [3,2,1]
  test1.sort(); //[1,2,3]
  

6.操作方法

    //concat会创建当前数组的一个副本再进行操作,不影响原数组
    const test = [1,2,3];
    const test1 = [1,2,3].concat([4,5]);
    
    console.log(test); //[1,2,3]
    console.log(test1); //[1,2,3,4,5]
    
    //slice接受一个或两个参数,返回起始和结束位置之间的项(但不包括最后位置的项),不影响原数组
    const test = [1,2,3,4];
    const test1 = test.slice(0); //[1,2,3,4]
    const test2 = test.slice(1,3); //[2,3]
    
    console.log(test); //[1,2,3,4] 原数组未改变
    
    //splice 可用作删除、插入和替换,改变原数组
    const test = [1,2,3,4,5];
    
    test.splice(1,2); //test为[1,4,5]
    
    test.splice(1,0,"a","b"); //test为[1,"a","b",2,3,,4,5]
    
    test.splice(2,1,"c"); //test为[1,2,"c",4,5]

7.位置方法

     const test = [1,2,3,4,5,4,3,2];
     test.indexOf(4); //3
     
     test.lastIndexOf(4); //5
     test.indexOf("4"); //-1  必须全相等(===)

8.循环方法

1.filter() 对数组的每一项允许给定函数,返回该函数会返回true的项组成数组,不会改变原数组

 const test = [1,2,3,4,5];
 const test1 = test.filter((item) => item > 3);
 
 console.log(test); //[1,2,3,4,5];
 console.log(test1); //[4,5];

2.map() 对数组的每一项执行给定函数,返回每次函数调用的结果组成的数组,不会改变原数组

   const test = [{a:1,b:2},{a:3,b:4},{a:5,b:6}];
   const test1 = test.map((item) => item['a']);
   
   console.log(test); //[{a:1,b:2},{a:3,b:4},{a:5,b:6}]
   console.log(test1); //[1,3,5]

3.forEach 对数组的每一项运行给定函数,没有返回新数组,没有返回值

         const test = [[1],[2]];
         test.forEach((item) => item.push(1));
         console.log(test); //[[1,1], [2,1]]     

9.其他的循环方法

1) 普通for循环(性能较好)
      const test = [1,2,3,4];
      for(let i=0,len=test.length;i<len;i++) {
           console.log(test[i]);
      }
      //1
      //2
      //3
      //4
2) for in 以【任意】顺序遍历一个对象的可枚举属性,所以不太建议用来遍历一个数组,原因如下。
for...in不应该用于迭代一个 Array,其中索引顺序很重要。数组索引只是具有整数名称的枚举属性,并且与通用对象属性相同。不能保证for ... in将以任何特定的顺序返回索引。for ... in循环语句将返回所有可枚举属性,包括非整数类型的名称和继承的那些,即它返回的除了数字索引外还有可能是你自定义的属性名字。
       const person = {work:"coder",age:"24",sex: "female"};
       for(prop in person) {
            console.log(`Jchermy[${prop}]=${person[prop]}`);
        }
        //Jchermy[work]=coder
        //Jchermy[age]=24
        //Jchermy[sex]=female
3)for..of  
语句在可迭代的对象上创建了一个循环(包括Array, Map, Set, 参数对象( arguments) 等等),
对值的每一个独特的属性调用一个将被执行的自定义的和语句挂钩的迭代。

for..of 作为es6中引进的循环,主要是为了补全之前for循环中的以下不足 :     
  • forEach 不能 break 和 return;
  • for-in 它不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到。而且,遍历数组元素的顺序可能是随机的。
 而相比之下for...of可以做到:
  • 与forEach 相比,可以正确响应 break, continue, return。
  • for-of 循环不仅支持数组,还支持大多数类数组对象,例如 DOM nodelist 对象。
  • for-of 循环也支持字符串遍历,它将字符串视为一系列 Unicode 字符来进行遍历。
  • for-of 也支持 Map 和 Set (两者均为 ES6 中新增的类型)对象遍历。
        let test = [3, 5, 7];
        test.foo = "hello";
        
        for (let i in test) {
           console.log(i); // "0", "1", "2", "foo"
        }
        
        for (let i of test) {
           console.log(i); // "3", "5", "7" // 注意这里没有 hello
        }
从以上我们可以看出for..of和for...in的区别

1. for...in循环出的是key,for...of循环出的是value
2. 作用于数组的for-in循环除了遍历数组元素以外,还会遍历自定义属性,比如例子中的foo属性。for...of循环不会循环对象的key,只会循环出数组的value。  

4)do...while 语句一直重复直到指定的条件求值得到假(false)

        let i = 0;
        do {
          i += 1;
          console.log(i);
        } while (i < 5);
        //1
        //2
        //3
        //4
        //5

5) while只要指定的条件为真就会一直执行它的语句块

    let n = 0;
    let x = 0;
    while(n<3) {
       n++;
       x +=n;
       console.log(n,xhdf);
    }
    // 1 1
    // 2 3
    // 3 6
    

三、常用数组操作

1.数组乱序

将一个数组完全打乱,然后返回打乱后的数组。也称为洗牌算法。

1) 利用Math.random()和sort()结合

   const test = [1,2,3,4];
   test.concat().sort(()=> Math.random() - 0.5); // [2, 4, 1, 3]
 

这个方法貌似可以实现我们要的结果,但是实际上它并不完全是随机的,而是越大的数字出现在越后面的概率越大。具体原因可以看这篇文章数组的完全随机排列

2) 遍历原数组,然后随机产生下标,将这个下标的值push到新的数组中,并随即删除这值,注意不是用delete,那样并不会改变数组的长度,效率不高,使用splice较好.

       function shuffle(array) {
          let i,n=array.length,copy=[];
          while(n) {
             i = Math.floor(Math.random()*n--);//n--是先与Math.random相乘再减一
             copy.push(array.splice(i, 1)[0]);
          }
          return copy;
       }
       const test = [1,2,3,4,5];
       console.log(shuffle(test));  //[2, 5, 4, 3, 1]

3)Fisher–Yates shuffle 算法 随机从数组中抽出一个元素,然后与最后个元素交换,相当于把这个随机抽取的元素放到了数组最后面去,表示它已经是被随机过了,同时被换走的那个元素跑到前面去了,会在后续的重复操作中被随机掉。一轮操作过后,下一轮我们只在剩下的n-1个元素也就是数组的前n-1个元素中进行相同的操作,直到进行到第一个。

    function shuffle(array) {
        let i, n=array.length;
        while(n) {
          i = Math.floor(Math.random()*(n--));
          
          t = array[i]
          array[i] = array[n];
          array[n] = t;
          n--;
        }
        return array;
    }
    var test = [1,2,3,4];
    console.log(shuffle(test.concat()));//传入数组的副本
    

2. 求斐波那契序列的某一项的值

ps:这一题的解法有很多种,以下仅列出几种

1) 首先祭出最经典的解法,利用递归求值。

     function fibonacci(n) {
         if(n==0 || n==1 ) {
            return n;
         }
         return fibonacci(n-1)+fibonacci(n-2);
     }
     const test = fibonacci(4); //3

这种方法的问题是很多值会重新求值,效率很低,因此并不推荐。
2)利用ES6的解构赋值

     const fibonacci =(n)=>{
        let a = 0;
        let b= 1;
        let i = 2;
        while(i++ <= n){
          [a, b] = [b, a+b]; //解构赋值
        }
        return b;
     }
     fibonacci(4); //3

3) 尾递归优化

递归非常耗内存,因为需要同时保存成千上百个调用帧,很容易发生‘栈溢出’。但对于尾递归优化来说,由于只存在一个调用帧,所以永远不会发生栈溢出。而尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数.
     function fibonacci(n, n1=0, n2=1){
         if(n <=1) {
            return n2;
         }
     return fibonacci(n-1, n2, n1 + n2);  
    
     }
     fibonacci(6); //8

4)利用缓存值减少重复求值

   function fibonacci(){
     var cache = {
        0:0,
        1:1
     }
     return function _fibonacci(n) {
         return typeof cache[n] === 'number' ?
         cache[n]:
         cache[n] = _fibonacci(n-1) + _fibonacci(n-2);
     }  
   }
   const f = fibonacci();
   f(9); //34

3.数组排序

1)最常用的利用sort()排序,仅适用于纯数字数组

  //升序
  function asc(arr){
    return arr.sort((a,b)=> {return a-b;})
  }
  asc([6,3,4,2]);//[2,3,4,6]

    //降序
  function desc(arr){
    return arr.sort((a,b)=> {return b-a;})
  }
  desc([4,3,2,5]); //[5, 4, 3, 2]

2)由对象组成的数组,支持根据对象的某个属性排序

  const sortByProp = (name)=>{
    return (f, s) => {
      var a, b;
      if (typeof f === 'object' && typeof s === 'object' && f && s) {
         a = f[name];
         b = s[name];
         if(a === b) {
            return 0;
         } else {
            return a < b ? -1 :1;
         }  
     } else {
        throw new TypeError('数组必须由对象组成');
     }
  };
};
const test = [{age:27, name:"xiaomi"},{age:17, name:"amy"},{age: 24, name: "Jchermy"}];
test.sort(sortByProp("age")); //[{age:17, name:"amy"},{age: 24, name: "Jchermy"}, {age:27, name:"xiaomi"}];

4.数组去重

1)利用array_filter()

   function unique(arr){
     let uniqueArr = [];
     uniqueArr = arr.filter((item) =>{
        return uniqueArr.includes(item) ? '' : uniqueArr.push(item);
     })
     return uniqueArr;
   };
   unique([1,2,3,1,6,3,2,7]); //[1,2,3,6,7];

2)利用es6中的Map()

   function unique(arr) {
     const seen = new Map();
     return arr.filter((item) => !seen.has(item) && seen.set(item, 1));
   }
   unique([11,2,3,4,4,2,5]); //[11, 2, 3, 4, 5]

3)利用es6中的Set()

    function unique(arr){
       return [...new Set(arr)]; //将set结构转为数组
    }
   unique([1,2,2,3,7,3,8,5]); //[1, 2, 3, 7, 8, 5]  

5.数组去除空值

function filter_array(array) {    
  return array.filter(item=>item);   
}   

const test = [undefined,undefined,1,'','false',false,true,null,'null'];     
filter_array(test);  //[1, "false", true, "null"]

四.结语

呼,写了好几天的文章终于写完啦,撒花~~?
如果你看到这里,觉得对你有所帮助的话,欢迎点赞收藏呀??
当然也非常欢迎大家对有纰漏的地方进行指正和建议,我一定会虚心接受的!希望大家能一起学习进步!


Jchermy
857 声望132 粉丝

Keep learning