JavaScript Array

348 阅读10分钟

1. 常用方法

1.1. Array.prototype.forEach()

遍历数组

语法:arr.forEach(callback(currentValue [, index [, array]])[, thisArg]);

callback:为数组中每个元素执行的函数,该函数接收三个参数:

  • currentValue:数组中正在处理的当前元素。
  • index:可选,数组中正在处理的当前元素的索引。
  • array:可选,forEach() 方法正在操作的数组。
  • thisArg:可选,可选参数。当执行回调函数 callback 时,用作 this 的值。
  • 返回值undefined

其不对稀疏数组中未初始化的值进行操作

1.2. Array.prototype.push()

push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度

改变原数组

1.3. Array.prototype.pop()

pop()方法从数组中删除最后一个元素,并返回该元素的值,此方法更改数组的长度。
注意:只弹出一个元素

改变原数组

1.4. Array.prototype.shift()

shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
注意:只删除一个元素

改变原数组

1.5. Array.prototype.unshift()

unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。

改变原数组

1.6. Array.prototype.indexOf()

返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

1.7. Array.prototype.reverse()

方法将数组中元素的位置颠倒,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。

该方法会改变原数组。

1.8. Array.prototype.slice()

语法arr.slice([begin[, end]]) shallowCopy 一个数组,这个在前文《JavaScript Clone》讲过,
slice(begin, end)方法返回一个新的数组对象,由 begin 和 end 决定的原数组的浅拷贝
包括 begin,不包括end.原始数组不会被改变。若不传入参数默认全部clone
可用于把类数组转换数组

1.9. Array.prototype.splice()

splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。

此方法会改变原数组。

【例】arr.splice(1, 2, "加1", "加2", ["+1", "+2", "+3"]);

1.10. Array.prototype.join()

join()方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。还可以指定连接符号,在()中传入参数符号即可

返回字符串但不改变原数组

附加String.prototype.split():使用指定的分隔符字符串将一个String对象分割成子字符串数组,以一个指定的分割字串来决定每个拆分的位置。
语法str.split([separator[, limit]])
separator:指定表示每个拆分应发生的点的字符串
limit:一个整数,限定返回的分割片段数量

这与join恰好相反

【例】

  var sss = [1,2,3,4,5,6];
  sss.join();
  //返回 "1,2,3,4,5,6"
  var arr1 = ["西红柿", "马铃薯", "四季豆"];
  console.log(arr1.join("&"));
  //输出:西红柿&马铃薯&四季豆

1.11. Array.prototype.sort()

sort() 方法用原地算法对数组的元素进行排序,并返回数组。

会改变原数组

默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16(Unicode的第三层)代码单元值序列时构建的,就像数字按由小到大排序时,9 出现在 80 之前,因为它会先比较9和8

由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。不过给出了相应接口让我们自己设置排序的规则

语法arr.sort([compareFunction])

  • compareFunction:可选,用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。
  • firstEl:第一个用于比较的元素。
  • secondEl:第二个用于比较的元素。
  • 返回值:排序后的数组。请注意,数组已原地排序,并且不进行复制。

当指明了compareFunction,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。

备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);

  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

根据属性排序

  var items = [{
      name: 'Edward',
      value: 21
    },
    {
      name: 'Sharpe',
      value: 37
    },
    {
      name: 'And',
      value: 45
    },
    {
      name: 'The',
      value: -12
    },
    {
      name: 'Magnetic'
    },
    {
      name: 'Zeros',
      value: 37
    }
  ];

  // sort by value
  arr.sort(function (a, b) {
    // 一般写法
    if (a.value != undefined) {
      return (a.value - b.value);
    } else {
      return -1;
    }
    // 三目运算
    // return (a.value != undefined) ? ((a.value - b.value)) : -1;
  });

  // sort by name,也就是按照首字母
  items.sort(function (a, b) {
    var nameA = a.name.toUpperCase(); // toUpperCase将名字字符串变成大写
    var nameB = b.name.toUpperCase(); // ignore upper and lowercase
    if (nameA < nameB) {
      return -1;
    }
    if (nameA > nameB) {
      return 1;
    }


    // names must be equal

    return 0;
  });
  console.log(items);

值得注意此处name转换为大写字母的过程,为何要如此?

还有若其中一个对象不存在该属性如何排序?还需考虑这些情况!

1.12. Array.prototype.concat()

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

值得注意concat()中传入的参数是一个时机的数组或者是一个数组的引用将会产生不一样的结果,比如

  var alpha = ['a', 'b', 'c'];
  var num = [1, [2, 3]];
  var alphaNumeric = alpha.concat(num);
  console.log(alphaNumeric);
  // output:(5) ["a", "b", "c", 1, Array(2)]
  var alphaNumeric = alpha.concat(1, [2, 3]);
  console.log(alphaNumeric);
  // output:(6) ["a", "b", "c", 1, 2, 3]

1.13. Array.prototype.filter()

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]

1.14. Array.from()

Array.from()方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

语法Array.from(arrayLike[, mapFn[, thisArg]])

arrayLike:想要转换成数组的伪数组对象或可迭代对象。

mapFn:可选,如果指定了该参数,新数组中的每个元素会执行该回调函数。

thisArg:可选,可选参数,执行回调函数 mapFn 时 this 对象。

【例】

  1. 从String生成数组Array.from('foo'); // 返回[ "f", "o", "o" ]
  2. 从set生成数组
      const set = new Set(['foo', 'bar', 'baz', 'foo']);
      Array.from(set);
      // [ "foo", "bar", "baz" ]
      ```
      1. 从map生成数组
      ```javascript
      const map = new Map([[1, 2], [2, 4], [4, 8]]);
      Array.from(map);
      // [[1, 2], [2, 4], [4, 8]]
      const mapper = new Map([['1', 'a'], ['2', 'b']]);
      Array.from(mapper.values());
      // ['a', 'b'];
      Array.from(mapper.keys());
      // ['1', '2'];

1.15. Array.prototype.map()

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一次提供的函数后的返回值。

1.16. 小结

改变原数组的额方法

  1. push
  2. pop
  3. shift
  4. unshift
  5. reverse
  6. splice
  7. join
  8. concat

不改变原数组的方法

  1. slice
  2. sort

2. 类数组

  1. 属性要为索引(数字)属性,必须要有length属性,最好加上push方法
  2. 可以当成数组一样用,但是类数组是对象,所以他的功能很强大

【例】

   var ooo = {
      "3": "a",
      "4": "b",
      "5": "c",
      name: "Tian",
      age: 22,
      length: 3,
      push: Array.prototype.push,
      spllice: Array.prototype.splice
   }
   ooo.push(["d", "e"]);
   console.log(ooo);

输出如上因为,push方法是以length为基础的,原理大致如下

      function push(target) {
         this[this.length] = target;
         this.length++;
      }

【例】判断输出

      var obj = {
         '2': 3,
         '3': 4,
         'length': 2,
         'splice': Array.prototype.splice,
         'push': Array.prototype.push
      }
      obj.push(1);
      obj.push(2);
      obj.push(3);
      console.log(obj);

这里的push进去的数字只代表值,不是指对应的索引,并且类数组的push是根据类数组的长度来的,输出如下

      Object(5) [empty × 2, 1, 2, 3, splice: ƒ, push: ƒ]
      2: 1
      3: 2
      4: 3
      length: 5
      push: ƒ push()
      splice: ƒ splice()
      __proto__: Object

3. 附加

3.1. ES6箭头函数

x => x * x相当于function (x) { return x * x;}

JavaScript数组是基于对象的,二者区别不大,访问数组中没有的属性也不会报错,但是访问没有的方法会报错,其行为比较松散

如果指定的索引是一个无效值,JavaScript 数组并不会报错,而是会返回 undefined,因为在 JavaScript 中,以数字开头的属性不能用点号引用,必须用方括号,比如arr1[2]是可以的,此处索引的是数组的一个元素,方括号中的2可以不加引号,但是在对象中或者其他情况,这种以数字开头的属性名都需要加引号

3.2. 给有序数组乱序

      var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
      arr.sort(function (a, b) {
         return Math.random() - 0.5;
      });
      console.log(arr);

3.3. 数组去重

注意:方法1、2、3、4各有自己的不完善之处,比如不能去除重复的NaN,或者相同的对象等问题,第六种是完善的

  1. 方法1:双重循环 (缺陷:所以不能去除NaN、相同的对象、相同的数组)
      Array.prototype.unique1 = function () {
         for (var i = 0; i < this.length; i++) {
            for (var j = i + 1; j < this.length; j++) {
            while (this[j] === this[i]) {
               this.splice(j, 1);
            }
            }
         }
      }
  1. 方法2:利用Set对象(缺陷:所以不能去除相同的对象、相同的数组)
var mySet = new Set([1, 2, 3, 4, 4, undefined, undefined, NaN, NaN, -0, +0]);
  [...mySet]; // (7) [1, 2, 3, 4, undefined, NaN, 0]
  1. 方法3:利用Map的has方法 (缺陷:所以不能去除相同的对象、相同的数组)
      function unique(array) {
         let result = new Array()
         let mapping = new Map()
         for (let item of array) {
            if (!mapping.has(item)) {
            // has返回一个布尔值,表示某个键是否在当前 Set 对象之中。
            mapping.set(item)
            result.push(item) //result本是空数组,利用Map的has函数构建起来
            }
         }
         return result
      }
  1. 方法4:利用indexOf单层循环(在新的数组里不断被访问,添加自己没有且被操作数组中有的元素)
    (缺陷:所以不能去除NaN、相同的对象、相同的数组)
      function unique(arr) {
         let res = [];
         for (let ind = 0, len = arr.length; ind < len; ind++) {
            let val = arr[ind];
            if (res.indexOf(val) === -1) res.push(val)
         }
         return res;
      }
  1. 也是双重循环
      function unique4(arr) {
         let res = []
         for (let i = 0, arrlen = arr.length; i < arrlen; i++) {
            let isUnique = true
            for (let j = 0, reslen = res.length; j < reslen; j++) {
            if (arr[i] === res[j]) {
               isUnique = false
               break
            }
            }
            if (isUnique) res.push(arr[i])
         }
         return res
      }
  1. 方法6:完美方法
      Array.prototype.unique6 = function () {
         var temp = {},
            arr = [],
            len = this.length;
         for (var i = 0; i < len; i++) {
            if (!temp[this[i]]) {
            temp[this[i]] = "随便";
            arr.push(this[i]);
            }
         }
         return arr;
      }

3.4. ES6 Map 与 Set

Map对象:Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

Set对象:允许你存储任何类型的唯一值(强调唯一值,因此可用于数组去重),无论是原始值或者是对象引用。

Maps 和 Objects 的区别

  1. 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值
  2. Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  3. Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算(关于Object属性检测前文中讲过)。
  4. Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

【例】

      var myMap = new Map();
      var keyString = "a string"; 
      myMap.set(keyString, "和键'a string'关联的值");
      myMap.get(keyString);    // "和键'a string'关联的值"
      myMap.get("a string");   // "和键'a string'关联的值"
      <!-- 因为 keyString === 'a string' -->

Set 中的特殊值 Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  1. +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
  2. undefined 与 undefined 是恒等的,所以不重复;
  3. NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。

Set对象的作用

  1. 差值计算(2种)
      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var difference = new Set([...a].filter(x => !b.has(x))); // {1}
      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var difference =new Set([...[...a].filter(x => !b.has(x)),...[...b].filter(x => !a.has(x))]); // {1,4}
  1. 数组去重
      var mySet = new Set([1, 2, 3, 4, 4]);
      [...mySet]; // [1, 2, 3, 4]
  1. 并集
      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var union = new Set([...a, ...b]); // {1, 2, 3, 4}
  1. 交集
      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}

4. 参考

  1. JavaScript实战笔记(二) 数组去重
  2. ES6基础之——new Set
  3. ES6 Map 与 Set
  4. MDN——Array