写在开头

在js中,数组应该是最常用的类型之一。并且,js中的数组和其他语言的数组有很大的区别。虽然都是有序列表,但是js中数组的每一项可以保存任何类型的值。也就是说,可以用数组的第一项来保存数值,第二项保存字符串,第三项保存对象,非常灵活。而且js数组的大小是具有呼吸性的,即可以随着数据的增加或删除自动改变length的大小。

常见的创建数组的方法有两种:一种是字面量语法,一种是使用Array构造函数创建。

var arr1 = [];                  // 创建了一个空数组
var arr2 = [1, 2, 3];           // 创建了一个包含3个数字的数组
var arr3 = ['a', 'b', 'c'];     // 创建了一个包含3个字符串的数组

var arr4 = new Array();         // 创建了一个空数组
var arr5 = new Array(10);       // 创建了一个length为10的数组,如果第一个参数不是数值,就会创建一个包含该项的数组
var arr6 = new Array('a', 'b')  // 创建了一个包含两个字符串的数组

我们用的最多的还是第一种方法

常用方法

检测数组

  • instanceof操作符:此方法基于原型/构造函数判断,只适用于只有一个全局环境的情况
arr instanceof Array;   // 是数组返回true,否则返回false
  • Array.prototype.isArray():ES5新增,为了解决instanceof的问题,存在兼容性。IE9+,Firefox 4+,Safari 5+,Opera 10.5+和Chrome支持
arr.isArray();   // 是数组返回true,否则返回false
  • 原型链:无兼容性
Object.prototype.toString.call(arr) === '[object Array]'

转换方法

  • Array.prototype.toString():数组每一项转换成字符串并用’,’拼接成一个字符串
['a', 'b', 'c'].toString();     // a,b,c
  • Array.prototype.join():使用不同的分隔符构建这个字符串,默认使用’,’
['a', 'b', 'c'].join();       // a,b,c
['a', 'b', 'c'].join('|');    // a|b|c

栈方法(后进先出)

  • Array.prototype.push():接收任意数量的参数,将它们逐个添加到数组末尾,返回修改后数组的长度
  • Array.prototype.pop():移除数组最后一项,并返回该项
var arr = [1, 2, 3]
var length = arr.push(4, 5);
console.log(arr, length);     // [1, 2, 3, 4, 5], 5
var item = arr.pop();
console.log(arr, item);       // [1, 2, 3, 4], 5

队列方法(先进先出)

  • Array.prototype.push():同上
  • Array.prototype.shift():移除数组第一项,并返回该项
var arr = []
var length = arr.push(1, 2);
console.log(arr, length);     // [1, 2], 2
var item = arr.shift();
console.log(arr, item);       // [2], 1

补充:Array.prototype.unshift():接收任意数量的参数,将它们逐个添加到数组开头,返回修改后数组的长度

var arr = [1, 2, 3]
var length = arr.unshift(4, 5);
console.log(arr, length);     // [4, 5, 1, 2, 3], 5

重排序方法

  • Array.prototype.reverse():翻转数组顺序,会改变原数组
console.log([1, 2, 3].reverse());   // [3, 2, 1]
  • Array.prototype.sort():由于翻转不够灵活而出现,可以自定义排序规则,如果不传函数参数,则数组每一项调用toString()方法,然后排序;会改变原数组
var arr = [1, 15, 2, 30];
arr.sort(function(a, b) {
  return a-b;
});
console.log(arr);     // [1, 2, 15, 30]

操作方法

  • Array.prototype.concat():先创建当前数组的一个副本,然后将接收到的参数添加到该副本的末尾。如果参数是一个或多个数组,会将这些数组的每一项添加到副本数组的末尾;如果不是数组,这些值就会简单的添加到副本数组的末尾;如果没传参数,就只是简单的创建一个副本。该方法返回这个新创建的副本数组,原数组不会改变。
var arr1 = [1, 2];
var arr2 = arr1.concat([3, 4], [5]);
var arr3 = arr1.concat(3, 4);
var arr4 = arr1.concat();
console.log(arr1);      // [1, 2]
console.log(arr2);      // [1, 2, 3, 4, 5]
console.log(arr3);      // [1, 2, 3, 4]
console.log(arr4);      // [1, 2]
  • Array.prototype.slice():基于当前数组的一或多项创建一个新数组,即截取数组。接收两个参数,第一个参数(起始位置)必须,第二个参数(结束位置,不截取该项)可选,若不传则默认截取从开始位置到当前数组末尾所有项,即数组的length-1。该方法返回截取的新数组,不会改变原数组。
var arr1 = [1, 2, 3, 4];
var arr2 = arr1.slice(1);
var arr3 = arr1.slice(1, 3);
console.log(arr1);        // [ 1, 2, 3, 4 ]
console.log(arr2);        // [ 2, 3, 4 ]
console.log(arr3);        // [ 2, 3 ]
  • Array.prototype.splice():最强大的数组操作方法,删除,插入,替换均可。接收多个参数,分别是操作位置,删除元素个数,新增的元素(可多个);该方法返回删除元素组成的数组(如果没有删除元素则返回一个空数组),会改变原数组。
var arr1 = [1, 2, 3];
var arr2 = arr1.splice(1, 1);
console.log(arr1);                      // [1, 3]
console.log(arr2);                      // [2]
var arr3 = arr1.splice(0, 0, 4, 5);
console.log(arr1);                      // [4, 5, 1, 3]
console.log(arr3);                      // []
var arr4 = arr1.splice(1, 1, 6);
console.log(arr1);                      // [4, 6, 1, 3]
console.log(arr4);                      // [5]

位置方法

  • Array.prototype.indexOf()
  • Array.prototype.lastIndexOf():这两个方法都接收两个参数,要查找的项(必须)和查找起点位置的索引(可选,包含该位置);找到了返回该项在数组中的索引,否则返回-1。不同的是前者从前向后查找,后者从后向前查找。
var arr = [1, 2, 3, 4, 5, 6, 4, 2];
console.log(arr.indexOf(4));        // 3
console.log(arr.indexOf(4, 4))      // 6
console.log(arr.indexOf(4, 7))      // -1
console.log(arr.lastIndexOf(4))     // 6
console.log(arr.lastIndexOf(4, 4))  // 3
console.log(arr.lastIndexOf(4, 2))  // -1

迭代方法

五个迭代方法:每个方法都接收两个参数,要在每一项上运行的函数(必须)和运行该函数的作用域对象(可选,影响this的值)。这个函数参数会接收三个参数,分别是迭代的每一项,该项的索引和数组对象本身。原数组都不会改变。

  • Array.prototype.every():函数参数对每一项都返回true,则返回true,否则返回false。
  • Array.prototype.filter():返回函数参数返回true的项组成的数组。
  • Array.prototype.forEach():没有返回值。
  • Array.prototype.map():返回每次调用函数参数的结果组成的数组。
  • Array.prototype.some():函数参数对任何一项返回true,则返回true,否则返回false。
var arr = [1, 2, 3, 4, 5];

var result1 = arr.every(function(item, index, arr) {
  return (item < 3);
});
console.log(result1);   // false

var result2 = arr.some(function(item, index, arr) {
  return (item < 3);
});
console.log(result2);   // true

var result3 = arr.filter(function(item, index, arr) {
  return (item < 3);
});
console.log(result3);   // [1, 2]

var result4 = arr.map(function(item, index, arr) {
  return (item * 2)
});
console.log(result4);   // [2, 4, 6, 8, 10]

arr.forEach(function(item, index, arr) {
  // 执行某些操作...
});

注意:函数参数的三个参数根据实际需要来确定是否接收,如不需要原数组对象,可以不接收。

缩小方法

  • Array.prototype.reduce():
  • Array.prototype.reduceRight():这两个方法都接收两个参数,一个在每一项调用的函数(必须)和作为缩小基础的初始值(可选)。不会改变原数组,并且构建出一个最终的值作为返回值。函数参数接收四个参数:前一项,当前项,当前项索引和原数组对象。这个函数返回的任何值都会作为下次调用该函数时的第一个参数,即前一项。第一次调用该函数时当前项是数组的第二项(即索引为1),第一个参数则为第一项(索引为0)。这两个函数的区别是前者从第一项开始,逐个遍历到最后;后者从最后一项开始,逐个遍历到第一项。
var arr = [1, 2, 3, 4];
var result = arr.reduce(function(pre, cur, index, arr) {
  return (pre + cur);
});
console.log(arr);       // [1, 2, 3, 4]
console.log(result);    // 10

ES6新增

  • Array.from():将类似数组的对象和可遍历的对象(包括ES6中的Set和Map)转化为真正的数组并返回该数组;字符串也能转化为数组
let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// es5
console.log(Array.prototype.slice.call(arrayLike, 0));    // ['a', 'b', 'c']

// es6
console.log(Array.from(arrayLike))    // ['a', 'b', 'c']

console.log(Array.from('hello'));     // ['h', 'e', 'l', 'l', 'o']

Array.from 方法还接受第二个参数,作用类似于数组的 map() 方法,用来对每个元素进行处理,将处理后的值放入返回的数组

let arrayLike = {
  '0': 1,
  '1': 2,
  '2': 3,
  length: 3
};
console.log(Array.from(arrayLike, x => x * x));       // [1, 4, 9]
// 等同于
console.log(Array.from(arrayLike).map(x => x * x));   // [1, 4, 9]
  • Array.of():将一组值转换为数组
Array.of(1,2,3);      // [1, 2, 3]
Array.of(3);          // [3]
Array.of();           // []
Array.of(1,,2,'a');   // error
Array.of(1,2,'a');    // [1,2,'a']
  • Array.prototype.copyWithin():在当前数组内部,将指定位置的成员,复制到其他位置(会覆盖原有成员,修改原数组),然后返回替换后的数组。该方法接收三个参数:(三个参数都是数值,如果不是会自动转化成数值)
    -target(必需):从该位置开始替换数据
    -start(可选):从该位置开始读取目标数据,默认值是0
    -end(可选):从该位置(不包括它)停止读取目标数据,默认值是数组长度
    (将start到end的成员替换从target开始的成员)
console.log([5, 3, 1, 8, 0, 2].copyWithin(3, 0, 2));    // [5, 3, 1, 5, 3, 2]
  • Array.prototype.find(): 该方法接收一个函数作为参数,用于找出参数函数返回true的第一个数组成员(即满足条件的成员),没找到返回 undefined。该函数参数接收三个参数(value,index,arr),分别是当前值,当前位置和原数组。
  • Array.prototype.findIndex():该方法参数同find(),返回符合条件的第一个数组成员的索引,没找到返回 -1。
console.log([1, 2, 3, 4].find((x) => x > 2))          // 3
console.log([1, 2, 3, 4].find((x) => x > 4))          // undefined
console.log([1, 2, 3, 4].findIndex((x) => x > 2))     // 2
console.log([1, 2, 3, 4].findIndex((x) => x > 4))     // -1
  • Array.prototype.fill():使用指定的值,填充一个数组并返回,会改变原数组。接收三个参数:
    -value(必需):填充的值
    -start(可选):填充的起始位置,默认是0
    -end(可选):填充的结束位置(不包括它),默认是数组的长度
var arr = [1, 2, 3, 4];
console.log(arr.fill(6));          // [6, 6, 6, 6]
console.log(arr);                  // [6, 6, 6, 6]
console.log(arr.fill(3,1,3));      // [6, 3, 3, 6]
console.log(arr);                  // [6, 3, 3, 6]
  • Array.prototype.entries(),Array.prototype.keys() 和 Array.prototype.values():这三个方法都返回一个遍历器对象,可以用for...of循环遍历,区别是keys()是对键名的遍历,values()是对键值的遍历,entries()是对键值对的遍历。
for (let key of [1, 2].keys()) {
  console.log(key);   // 0, 1
}
for (let value of [1, 2].values()) {
  console.log(value);
}
for (let [key, value] of [1, 2].entries()) {
  console.log(key, value);   // 0 1 , 1 2
}

注意:对于values(), Chrome 未实现,Firefox未实现,Edge已实现。

  • Array.prototype.includes():该方法返回了一个布尔值,判断数组中是否包含给定的值,与字符串的 includes() 类似。该方法可以检测到 NaN,弥补了 indexOf() 的不足;并且 indexOf() 不够具有语义化,它检测的是在数组中的位置。它接收两个参数:
    -target(必需):需要在数组中匹配的值
    -start(可选):从该位置(包含该位置)开始匹配,默认为0。如果为负数则表示倒数的位置,如果大于数组长度则重置为0
console.log([1, 2, 3].includes(2))          // true
console.log([1, 2, 3].includes(2, 2))       // false
console.log([1, 2, 3].includes(2, -2))      // true

注意:该方法目前还存在兼容性

// 兼容性写法
const contains = (() => 
  Array.prototype.includes 
    ? (arr, value, index) => arr.includes(value, index)
    : (arr, value, index) => arr.some((el, index) => el === value)
)();

console.log(contains([1,2,3], 1, 1));    // false

另外:Map 和 Set 数据结构有一个 has 方法,注意与 includes() 的区别:
-Map:查找键名(key)
-Set:查找键值(value)

参考:JavaScript高级程序设计(第三版) 阮一峰es6入门:数组的扩展

写在最后

在如此多的数组方法中,非常容易记混。我认为要抓住以下几点:该方法的作用?所需的参数(必需的和可选的)有哪些?是否有返回值?返回值是什么?是否会改变原数组?