数组API梳理

145 阅读8分钟

先思考几个问题:

  1. 数组的构造器有哪几种?
  2. 哪些是改变自身的方法?
  3. 哪些是不改变自身的方法?
  4. 遍历的方法有哪些?

1. 数组概念的探究

截至 ES7 规范,数组共包含 33 个标准的 API 方法和一个非标准的 API 方法

2. 改变自身的方法

基于 ES6,会改变自身值的方法一共有 9 个,分别为 pop、push、reverse、shift、unshift、sort、splice,以及两个 ES6 新增的方法 copyWithin 和 fill。

// pop方法  移除数组的最后一个元素,并返回该元素
var array = ["cat", "dog", "cow", "chicken", "mouse"];
var item = array.pop();
console.log(array);   // ["cat", "dog", "cow", "chicken"]
console.log(item);   // mouse

// push方法  向数组末尾添加新项目,并返回新长度
var array = ["football", "basketball",  "badminton"];
var i = array.push("golfball");
console.log(array);   // ["football", "basketball", "badminton", "golfball"]
console.log(i);   // 4

// reverse方法  
var array = [1,2,3,4,5];
var array2 = array.reverse();
console.log(array);   // [5,4,3,2,1]
console.log(array2===array);   // true

// shift方法  移除数组的第一个元素,并返回该元素
var array = [1,2,3,4,5];
var item = array.shift();
console.log(array); // [2,3,4,5]
console.log(item); // 1

// unshift方法  将新项添加到数组的开头,并返回新的长度
var array = ["red", "green", "blue"];
var length = array.unshift("yellow");
console.log(array);    // ["yellow", "red", "green", "blue"]
console.log(length);   // 4

// sort方法  默认按字母和升序将值作为字符串进行排序
var array = ["apple","Boy","Cat","dog"];
var array2 = array.sort();
console.log(array);   // ["Boy", "Cat", "apple", "dog"]
console.log(array2 == array);   // true

// splice方法  向/从数组添加/删除项目,并返回删除的项目
var array = ["apple","boy"];
var splices = array.splice(1,1);
console.log(array);   // ["apple"]
console.log(splices);   // ["boy"]

// copyWithin方法  将数组元素复制到数组中的另一个位置,覆盖现有值  //(target, start, end)
var array = [1,2,3,4,5]; 
var array2 = array.copyWithin(0,3);
console.log(array===array2,array2);  // true [4, 5, 3, 4, 5]

// fill方法  用静态值填充数组中的指定元素   //(value, start, end)
var array = [1,2,3,4,5];
var array2 = array.fill(10,0,3);
console.log(array===array2,array2);   // true [10, 10, 10, 4, 5]

结合 Leetcode 中的第 88 题 《合并两个有序数组》,看下如何利用数组的多个改变自身的方法来解决这道题,题目是这样的:

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
  输入:
  nums1 = [1,2,3,0,0,0]; m = 3
  nums2 = [2,5,6];       n = 3
  输出: [1,2,2,3,5,6]

仔细看下题目要求:

  • 首先是将 nums2 合并到 nums1 里面,不新开数组,否则将无法通过;
  • 其次是合并完了之后 nums1 还是一个有序数组,这里也是需要注意的;
  • 另外样例里面 nums1 和 nums2 都有“2”这个数,也都需要将重复的合并进去。

思考下,既然要求不能新开数组,就需要利用数组改变自身的方法完成这个题目。可以巧妙地利用数组的 API 中的 splice、push、sort 这三个方法,在原数组上进行操作,最终实现。

var merge = function (nums1, m, nums2, n) {
    nums1.splice(m);
    nums2.splice(n);
    nums1.concat(nums2);
    nums1.sort((a, b) => a - b);  
};

3. 不改变自身的方法

基于 ES7,不会改变自身的方法也有 9 个,分别为 concat、join、slice、toString、toLocaleString、indexOf、lastIndexOf、未形成标准的 toSource,以及 ES7 新增的方法 includes

// concat方法
var array = [1, 2, 3];
var array2 = array.concat(4,[5,6],[7,8,9]);
console.log(array2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(array); // [1, 2, 3], 可见原数组并未被修改

// join方法
var array = ['We', 'are', 'Chinese'];
console.log(array.join()); // "We,are,Chinese"
console.log(array.join('+')); // "We+are+Chinese"

// slice方法
var array = ["one", "two", "three","four", "five"];
console.log(array.slice()); // ["one", "two", "three","four", "five"]
console.log(array.slice(2,3)); // ["three"]

// toString方法
var array = ['Jan', 'Feb', 'Mar', 'Apr'];
var str = array.toString();
console.log(str); // Jan,Feb,Mar,Apr

// tolocalString方法
var array= [{name:'zz'}, 123, "abc", new Date()];
var str = array.toLocaleString();
console.log(str); // [object Object],123,abc,2016/1/5 下午1:06:23

// indexOf方法
var array = ['abc', 'def', 'ghi','123'];
console.log(array.indexOf('def')); // 1

// includes方法
var array = [-0, 1, 2];
console.log(array.includes(+0)); // true
console.log(array.includes(1)); // true
var array = [NaN];
console.log(array.includes(NaN)); // true

其中 includes 方法需要注意的是,如果元素中有 0,那在判断过程中 +0 和 -0 都会判断为 True。

另外还有一个值得强调的是slice 不改变自身,而 splice 会改变自身。其中,slice 的语法是:arr.slice([start[, end]]),而 splice 的语法是:arr.splice(start,deleteCount[, item1[, item2[, …]]])。

此外,lastIndexOf 和 indexOf 基本功能差不多,不过 lastIndexOf 是从后面寻找元素的下标。

4. 数组遍历的方法

基于 ES6,不会改变自身的遍历方法一共有 12 个,分别为 forEach、every、some、filter、map、reduce、reduceRight,以及 ES6 新增的方法 entries、find、findIndex、keys、values。


// forEach方法
var array = [1, 3, 5];
var obj = {name:'cc'};
var sReturn = array.forEach(function(value, index, array){
  array[index] = value;
  console.log(this.name);  // cc被打印了三次, this指向obj
},obj);
console.log(array);   // [1, 3, 5]
console.log(sReturn);  // undefined

// every方法
var o = {0:10, 1:8, 2:25, length:3};
var bool = Array.prototype.every.call(o,function(value, index, obj){
  return value >= 8;
},o);
console.log(bool);  // true

// some方法
var array = [18, 9, 10, 35, 80];
var isExist = array.some(function(value, index, array){
  return value > 20;
});
console.log(isExist);  // true 

// map 方法
var array = [18, 9, 10, 35, 80];
const newArr = array.map(item => item + 1);
console.log(array);   // [18, 9, 10, 35, 80]
console.log(newArr);  // [19, 10, 11, 36, 81]

// filter 方法
var array = [18, 9, 10, 35, 80];
var array2 = array.filter(function(value, index, array){
  return value > 20;
});
console.log(array2);  // [35, 80]

// reduce方法
var array = [1, 2, 3, 4];
var s = array.reduce(function(previousValue, value, index, array){
  return previousValue * value; 
},1);
console.log(s);  // 24
// ES6写法更加简洁
array.reduce((p, v) => p * v);  // 24

// reduceRight方法 (和reduce的区别就是从后往前累计)
var array = [1, 2, 3, 4];
array.reduceRight((p, v) => p * v);  // 24

// entries方法
var array = ["a", "b", "c"];
var iterator = array.entries();
console.log(iterator.next().value);  // [0, "a"]
console.log(iterator.next().value);  // [1, "b"]
console.log(iterator.next().value);  // [2, "c"]
console.log(iterator.next().value);  // undefined, 迭代器处于数组末尾时, 再迭代就会返回undefined

// find & findIndex方法  返回符合条件数组的第一个元素
var array = [1, 3, 5, 7, 8, 9, 10];
function f(value, index, array){
  return value % 2 === 0;    
}
function f2(value, index, array){
  return value > 20;    
}
console.log(array.find(f));  // 8
console.log(array.find(f2));  // undefined
console.log(array.findIndex(f));  // 4
console.log(array.findIndex(f2));  // -1

// keys方法
[...Array(10).keys()];      // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[...new Array(10).keys()];  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// values方法
var array = ["abc", "xyz"];
var iterator = array.values();
console.log(iterator.next().value);  //abc
console.log(iterator.next().value);  //xyz

注意有些遍历方法不会返回处理之后的数组,比如 forEach;有些方法会返回处理之后的数组,比如 filter。

reduce 方法也需要重点关注,其参数复杂且多,通常使用 reduce 很容易就可以解决一些复杂的逻辑处理。

首先是 callback(一个在数组的每一项中调用的函数,接受四个参数):

  1. previousValue(上一次调用回调函数时的返回值,或者初始值)
  2. currentValue(当前正在处理的数组元素)
  3. currentIndex(当前正在处理的数组元素下标)
  4. array(调用 reduce() 方法的数组)

然后是 initialValue(可选的初始值,作为第一次调用回调函数时传给 previousValue 的值)。

/* 题目:数组 arr = [1,2,3,4] 求数组的和:*/
// 第一种方法:
var arr = [1,2,3,4];
var sum = 0;
arr.forEach(function(e){sum += e; return sum}); // sum = 10
console.log(sum)

// 第二种方法
var arr = [1,2,3,4];
var sum = 0;
arr.map(function(obj){sum += obj});

// 第三种方法
var arr = [1,2,3,4];
arr.reduce(function(pre,cur){return pre + cur});

可以看出,用 forEach 和 map 都能实现数组的求和,其中需要另外新定义一个变量 sum,再进行累加求和,最后再来看 sum 的值,而 reduce 不仅可以少定义一个变量,而且也会直接返回最后累加的结果。

题目:  var arr = [ {name: 'brick1'}, {name: 'brick2'}, {name: 'brick3'} ], 希望最后返回到 arr 里面每个对象的 name 拼接数据为 'brick1, brick2 & brick3' ,用 reduce 实现。

var arr = [{ name: 'one' }, { name: 'two' }, { name: 'three' }];
const data = arr.reduce(function (prev, current, index, array) {
    if (index === 0) {
        return current.name;
    } else if (index === array.length - 1) {
        return prev + ' & ' + current.name;
    } else {
        return prev + ', ' + current.name;
    }
}, '');
console.log(data); //  "one, two & three"

5. 总结

image.png

数组的各方法之间存在很多共性,如下:

  • 所有插入元素的方法,比如 push、unshift 一律返回数组新的长度;
  • 所有删除元素的方法,比如 pop、shift、splice 一律返回删除的元素,或者返回删除的多个元素组成的数组;
  • 部分遍历方法,比如 forEach、every、some、filter、map、find、findIndex,它们都包含 function(value,index,array){} 和 thisArg 这样两个形参。

6. 补充

slice(数组)

用法:array.slice(start, end)

//如果不传入参数二,那么将从参数一的索引位置开始截取,一直到数组尾
var a=[1,2,3,4,5,6];
var b=a.slice(0,3);    //[1,2,3]
var c=a.slice(3);       //[4,5,6]
 
//如果两个参数中的任何一个是负数,array.length会和它们相加,试图让它们成为非负数,举例说明:
//当只传入一个参数,且是负数时,length会与参数相加,然后再截取
var a=[1,2,3,4,5,6];
var b=a.slice(-1);    //[6]
 
//当只传入一个参数是负数,并且参数的绝对值大于数组length时,会截取整个数组
var a=[1,2,3,4,5,6];
var b=a.slice(-6);    //[1,2,3,4,5,6]
var c=a.slice(-8);    //[1,2,3,4,5,6]
 
//当传入两个参数一正一负时,length也会先于负数相加后,再截取
var a=[1,2,3,4,5,6];
var b=a.slice(2,-3);    //[3]
 
//当传入一个参数,大于length时,将返回一个空数组
var a=[1,2,3,4,5,6];
var b=a.slice(6);  //[]

取数组最后一个元素

实现1:arr[arr.length-1],假如是空数组取的是 arr[-1],比较奇怪

可以使用 [...arr].pop() 或者 arr.splice(-1)[0]