前言

数组是日常开发中最常见的数据类型,很多场景一个for循环基本都可以实现。但是想要更高效、更准确的开发,数组的使用是要了解的很透彻才行。本文从数组的遍历和操作两个角度来讲解数组的使用。

数组遍历

for

for是最常见的使用方式,遍历数组的同时可以对数组项进行处理,循环中的处理会改变原数组。无返回值。
例:

let arr = [1, 2, 3, 4, 5]
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i])
}
输出:
// 1
// 2
// 3
// 4
// 5

for (语句 1; 语句 2; 语句 3) {
     要执行的代码块
}
语句1在循环(代码块)开始之前执行
语句1定义运行循环(代码块)的条件
语句3会在循环(代码块)每次被执行后执行

for循环支持退出循环,break或者continue,break为中止循环。continue为中止本次循环,其他循环会继续。

let arrA = [1, 2, 3, 4, 5]
for (let i = 0; i < arrA.length; i++) {
  console.log(i)
  if(i === 2) break;
}
输出:
// 0
// 1
// 2

let arrA = [1, 2, 3], arrB = ['a', 'b', 'c', 'd', 'e']
condition0:
for (let i = 0; i < arrA.length; i++) {
  condition1:
  for (let j = 0; j < arrB.length; j++) {
    if(j === 1) break condition0;
    console.log(`${arrA[i]} ${arrB[j]}`)
  }
}
输出:
// 1 a
break 不加‘条件值’时退出父循环,加了条件值则退出‘条件值’定义的循环。

let arrA = [1, 2, 3], arrB = ['a', 'b', 'c']
for (let i = 0; i < arrA.length; i++) {
  for (let j = 0; j < arrB.length; j++) {
    if(j === 1) continue;
    console.log(`${arrA[i]} ${arrB[j]}`)
  }
}
输出:
// 1 a
// 1 c
// 2 a
// 2 c
// 3 a
// 3 c

for of和for in

for of可以遍历数组元素,for in可以遍历数组下标

let a = ['a', 'b', 'c', 'd', 'e']
for(let i of a){
    console.log(i)
}
输出:
// a
// b
// c
// d
// e

let a = ['a', 'b', 'c', 'd', 'e']
for(let i in a){
    console.log(i)
}
输出:
// 0
// 1
// 2
// 3
// 4

forEach

很常见的比较简洁的数组遍历方式,遍历数据每一项,不可中断,没有返回值。
例:

let arr = [1, 2, 3, 4, 5]
arr.forEach(a => {
    console.log(a)
])
输出:
// 1
// 2
// 3
// 4
// 5

some

对数组中每一项运行制定函数,如果函数对任一项返回true,则返回true,同时中断循环。
例:

let arr = [1, 2, 3, 4, 5]
arr.some(a => {
    console.log(a);
    return a > 3
})
输出:
// 1
// 2
// 3
// 4

every

对数组中每一项运行指定函数,如果函数对每一项都返回true,则返回true,同时中断循环。换个角度讲,只有有任一项返回false,则返回false。
例:

let arr = [1, 2, 3, 4, 5]
let res = arr.every(a => {
    console.log(a)
    return a > 3
})
输出:
// 1
console.log(res) // false

map

对数组中每一项运行指定函数,返回每次函数调用的结果返回的数组,不可中断,返回新的数组。
例:

let arr = [1, 2, 3, 4, 5]
let newArr = arr.map(a => {
    return a + 1
})
console.log(newArr) // [2, 3, 4, 5, 6]

filter

对数组每一项运行指定函数,返回该函数执行后为true的数组项组成的数组,不可中断,返回新的数组。
例:

let arr = [1, 2, 3, 4, 5]
let newArr = arr.filter(a => {
    return a % 2 === 0
})
console.log(newArr)
输出:
// [2, 4]

find

对数组中每一项运行指定函数,如果函数对任一项返回true,则返回true,同时中断循环,返回执行为true的数组项。
例:

let arr = [1, 2, 3, 4, 5]
let item = arr.find(a => {
    console.log(a)
    return a % 2 === 0
})
输出:1 2
console.log(item) // 2

findIndex

对数组中每一项运行指定函数,如果函数对任一项返回true,则返回true,同时中断循环,返回执行为true的数组项的index下标。
例:

let arr = [1, 2, 3, 4, 5]
let index = arr.findIndex(a => {
    return a % 2 === 0
})
console.log(index)
输出:
// 1

数组操作

生成

最简单暴力的方式。

let a = [1, 2, 3, 4, 5]
Array.from

只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组。

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

// arguments对象
function foo() {
  var args = Array.from(arguments);
  // ...
}

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

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

Array.from转换字符串,传入数字直接生成数组。

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

Array.from({length:3})
// [undefined, undefined, undefined]

Array.from配合fill使用可以生成带有值的数组,支持配置三个参数 填充值 + 起始位置 + 结束位置。

let a = Array.from({length:3}).fill(1)
console.log(a) // [1, 1, 1]
Array.of

Array.of方法用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

上面代码中,Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。

扩展运算符 …
let a = [1, 2, 3, 4, 5]
let b = a
let c = [...a]

console.log(c) // [1, 2, 3, 4, 5]

扩展运算符可以用作拷贝,但是要注意是浅拷贝
let arr = [{a:1},2,3]
let arr_0 = arr;
let arr_1 = [...arr]
arr[0].a = 11
arr[1] = 22
console.log(arr_0[0].a, arr_0[1]) // 11 22
console.log(arr_1[0].a, arr_1[1]) // 11 2
entries(),keys()和values()

ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

如果不使用for…of循环,可以手动调用遍历器对象的next方法,进行遍历。

let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

添加/合并/删除

push、pop、concat、unshift和shift

push支持项数组末尾添加项。

let a = [1, 2]
a.push(3)
console.log(a) // [1, 2, 3]

Array的原型加apply可以实现通过push添加多个项。

let a = [1, 2]
Array.prototype.push.apply(a, [3, 4])
console.log(a) // [1, 2, 3, 4]

pop为删除最后一项,返回被删除的数组项。

let a = [1, 2]
let b = a.pop()
console.log(b) // 2

concat可以合并两个数组,原数组数据不变,返回新数组。

let a = [1, 2], b = [3, 4]
let c = a.concat(b)
console.log(a) // [1, 2]
console.log(b) // [3, 4]
console.log(c) // [1, 2, 3, 4]

unshift支持像数组首位添加项。

let a = [1, 2]
a.unshift(3)
console.log(a) // [3, 1, 2]

Array的原型加apply可以实现通过unshift实现像首位添加多个项。

let a = [1, 2]
Array.prototype.unshift.apply(a, [3, 4])
console.log(a) // [3, 4, 1, 2]

shift可以删除数组首位数组项,返回被删除元素。

let a = [1, 2, 3, 4]
let b = a.shift()
console.log(a) // [2, 3, 4]
console.log(b) // 1
splice

splice可用来 添加/删除 数组项,添加时 传入 3 个参数,起始位置 + 删除数量 + 添加项。

let a= [1, 2, 3]
a.splice(0, 0, 4)
console.log(a) // [4, 1, 2, 3]

a.splice(0, 1)
console.log(a) // [1, 2, 3]
slice

slice用来从已存在数组中选中部分元素,传入 2 个参数,起始位置(包含) + 结束位置(不包含)。

let a = [1, 2, 3, 4, 5]
let b = a.slice(0, 2)
console.log(b) // [1, 2]
reduce和reduceRight

reduce和reduceRight都会迭代数组的所有项,然后构建一个最终返回的值。
其中,reduce()方法从数组的第一项开始,逐个遍历到最后。reduceRight()则从数组的最后一项开始,向前遍历到第一项。
两个方法都接受两个参数,一个在每一项上调用的函数 + 作为归并基础的初始值(可选)。
传给reduce()和reduceRight()的函数接收4个参数:前一个值、当前值、项的索引和数组对象。
这个函数返回的任何值都会作为第一个参数自动传给下一项。
第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。

let arr = [1, 2, 3, 4, 5]
let sum = arr.reduce((pre, cur, index, array) => {
    return pre + cur
})
console.log(sum) // 15
a[n] = 1 通过长度控制

数组的length(长度)并不是只读属性,可以通过更改长度值更改数组项。

let a = [1, 2, 3, 4]
a.length = 4
console.log(a) // [1, 2, 3, 4]
a[4] = 6
console.log(a) // [1, 2, 3, 4, 6]
copyWithin

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。
也就是说,使用这个方法,会修改当前数组。传入 3 个参数 target(从该位置开始替换数据。如果为负值,表示倒数。必填) + start(从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。可选) + end(到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。可选)。

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
flat

数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]

flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]

如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

如果原数组有空位,flat()方法会跳过空位。

[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]
flatMap

flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

flatMap()只能展开一层数组。

// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]

查询 includes find findIndex

includes

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES2016 引入了该方法。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

find和findIndex上面有讲,根据运行函数返回数组项,一个是返回数组项,一个是返回数组下标。

排序

reverse

reverse是反向排序。

let a = [1, 2, 3, 4, 5]
a.reverse()
console.log(a) // [5, 4, 3, 2, 1]

sort是根据函数(可选)对数组进行排序,改变原数组。函数存在两个参数 前序值 + 后序值。
如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。

let a = [10, 45, 1000, 1, 20]
a.sort((a, b) => {
    return a - b
})
console.log(a) // [1, 10, 20, 45, 1000]

let b = ['George', 'John', 'Thomas', 'James', 'Adrew', 'Martin']
b.sort()
console.log(b) // ["Adrew", "George", "James", "John", "Martin", "Thomas"]

转换

join

使用分隔符分割数组成字符串,默认分隔符为‘,'逗号。

let a = [1, 2, 3, 4, 5]
let b = a.join()
let c = a.join('_')
console.log(b) // 1,2,3,4,5
console.log(c) // 1_2_3_4_5
toString

转换为以‘,’逗号分割的字符串。

let a = [1, 2, 3, 4, 5]
let b = a.toString()
console.log(b) // 1,2,3,4,5

位置操作

indexOf返回第一个匹配的数组项的下标。
lastIndexOf返回最后一个匹配数组项的下标。
findIndex前面有提,返回的是第一个函数运行为true的数组项的下标。

let a = [1, 2, 3, 3, 4, 5]
let b = a.indexOf(3)
let c = a.lastIndexOf(3)
let d = a.findIndex(a => {
    return a === 3
})

console.log(b) // 2
console.log(c) // 3
console.log(d) // 2