数组中有很多方法,在日常开发中合理选择方法可以简化我们的代码。当然合理选择的前提就是你对数组上方法了然于胸,说到数组脑海中自然而然的就浮现出push、shift、filter、foreach、map等等方法,并且对他们的参数、返回值以及使用场景十分清晰。这篇文章就重点总结数组上的方法,做一个全面的总结。我在这里将他们分成两个大区域:ES6之前和ES6之后

ES6之前

toString

1
2
3
4
5
let arr = [1,2,3,4,5]
console.log(arr.toString()) // 1,2,3,4,5 转换成string类型 说白了就是去除两边的[]

let arr1 = [1,[2,3,[4,5,[6,7,[8,[9]]]]]]
console.log(arr1.toString()) //1,2,3,4,5,6,7,8,9
  • toString方法可以将我们的数组转换成字符串
  • toString方法可以将我们的多维数组扁平化后转换成字符串

join

1
2
3
4
5
6
let arr = [1,2,3,4,5]
console.log(arr.join('-')) // 1-2-3-4-5

//多维数组
let arr = [1,2,[3,4,[5,6]],7]
console.log(arr.join('-')) //1-2-3,4,5,6-7
  • join方法可以将我们的数组按照指定字符进行拼接 默认值是逗号 也就是默认情况下和toString效果一样
  • 当然当涉及到多维数组的情况下,他们的表现有不一致,他只能处理第一层,如果使用默认值,他的表现和toString一致,但是这不是join的功劳,嵌套在里面的数组默认就是用逗号分隔的,而不是`join`方法的默认值起的作用

思考下join和String.prototype.split 方法的区别:他们属于逆运算

1
2
3
4
let arr = [1,2,[3,4,[5,6]],7]
let str = arr.join()
console.log(str,'str') // 1,2,3,4,5 str
console.log(str.split(",")) // ["1", "2", "3", "4", "5"] 每一项的类型变了

数组的堆栈方法 push | pop | unshift | shift

1
2
3
4
5
let arr = [1, 2, 3]
console.log(arr, arr.push(9)); //[1,2,3,9] 4
console.log(arr, arr.pop()); //[1,2,3] 9
console.log(arr, arr.unshift(9)); // [9,1,2,3] 4
console.log(arr, arr.shift()); // [1,2,3] 9
  • 这四个方法常见、常用,两个头尾新增,两个头尾移除。新增返回长度,移除返回移除的项

数组的排序 reverse | sort

​ 这两个方法都是用来对数组进行排序的,其中reverse方法是将数组倒序处理,而sort方法根据回调函数能做到不同的效果,他们操作的都是原数组

1
2
3
4
5
6
7
8
9
let arr = [1, 2, 3, 4, 5]
console.log(arr.reverse()); // [ 5, 4, 3, 2, 1 ]
console.log(arr); // [ 5, 4, 3, 2, 1 ]

let arr1 = [1, 2, 3, 4, 5]
const sortArr = arr1.sort((a, b) => b - a) // 也可以实现逆序数组
console.log(sortArr, 'sortArr'); // [ 5, 4, 3, 2, 1 ] sortArr
console.log(arr1, 'arr1'); // [ 5, 4, 3, 2, 1 ] arr1
console.log(sortArr === arr1); //true

数组拼接 concat

1
2
3
4
5
let arr = [1, 2]
console.log(arr.concat(3, 4)); // [ 1, 2, 3, 4 ]
console.log(arr.concat([3, 4])); //[ 1, 2, 3, 4 ]
console.log(arr.concat([5, [6, 7]])); //[ 1, 2, 5, [ 6, 7 ] ]
console.log(arr); //[ 1, 2 ]
  • 经过上面几个demo,可以发现concat返回的是一个新的数组,原数组不受影响
  • concat方法可以简单处理单层数组,多维数组的合并就并不会进行扁平化

数组的删改 slice | splice (左开右闭)

  1. slice:左开右闭区间:第一个参数start 是开始的下标(包含),第二个参数end 是结束的下标(不包含),如果没有结束参数 默认从start一直截取到最后

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    let arr = [1, 2, 3, 4]
    const newArr = arr.slice() // 可以用于生成一个新的数组
    console.log(arr == newArr); // false

    console.log(arr, 'arr'); // [ 1, 2, 3, 4 ] arr 原数组没有有受到影响
    console.log(arr.slice(2), 'slice'); // [ 3, 4 ] slice
    console.log(arr.slice(2, 3)); // [3] 从下标2开始(包含) 到下标为3的项(不包含)

    //如果给负数的情况下 就是意味着从后往前开始计算 最后一个为-1,一次往前推,同样满足左开右闭
    console.log(arr.slice(-3, -1)); //[2,3]
  2. splice:同样满足左开右闭区间,和slice最大的区别就是这个会影响原数组,其次根据参数的不同,他可以做到删除、插入、修改等操作

    1
    2
    3
    let arr1 = [1, 2, 3, 4, 5]
    console.log(arr1.splice(2), 'splice'); // [ 3, 4, 5 ] splice
    console.log(arr1, 'arr1'); // [ 1, 2 ] arr1 原数组收到了影响
    • 参数:第一个参数是开始的位置,第二个参数是截取的长度,第三个参数是插入什么元素

      1
      2
      3
      4
      5
      6
      7
      let arr1 = [1, 2, 3, 4, 5]

      console.log(arr1.splice(2, 3, 7, 8, 9)); // [ 3, 4, 5 ] 返回的是删除了哪些元素
      console.log(arr1); // [ 1, 2, 7, 8, 9 ] 可以发现原数组发生变化了,从index为2开始删除3个元素,插入6、7、8

      console.log(arr1.splice(2, 0, 10, 11, 12)); // [] 删除0个 所以返回空数组
      console.log(arr1); //[1, 2, 10, 11, 12, 3, 4, 5] 可以看出是插入在下标为2的前面 说白了就是把需要插入的元素从下标为2开始进行插入操作

数组的查找 indexOf | lastIndexOf

  • 返回的是下标
1
2
3
4
5
6
let arr = [1, 2, 3, 4, 5]

console.log(arr.indexOf(3)); // 2
console.log(arr.lastIndexOf(3));// 2
console.log(arr.indexOf(31)); // -1
console.log(arr.lastIndexOf(31));// -1

ES6之后

fill 填充数组

​ 第一个参数是要填充的元素,第二个参数是开始的下标,第三个参数是结束的下标,默认全部填充,同样满足左闭右开区间特性

1
2
3
4
let arr = new Array(5)
console.log(arr); // [ <5 empty items> ]
console.log(arr.fill(9, 0, 3)); // [ 9, 9, 9, <2 empty items> ]
console.log(arr); // [ 9, 9, 9, <2 empty items> ]

find | findIndex

​ 数组实例的find()方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

​ 数组实例的findIndex()方法的用法与find()方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

1
2
3
4
5
let arr = [1, 2, 3, 4, 5, 6, 7]
console.log(arr.find((item) => item > 3)); // 4
console.log(arr.findIndex((item) => item > 3));// 3
console.log(arr.find((item) => item > 31)); // undefined
console.log(arr.findIndex((item) => item > 31)); // -1

Array的静态方法

Array.of

​ 使用该方法可以生成一个由参数组成的数组

1
console.log(Array.of(2, 3, 4)); // [2, 3, 4]

Array.from

​ 使用该方法同样可以生成一个有参数组成的数组,但是他在传递类数组表现和Array.of却并不一致,form方法可以将类数组转换成真正的数组

1
2
3
4
5
6
7
function foo() {
console.log(Array.of(arguments));
//[ [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 } ]
console.log(Array.from(arguments)); // [ 1, 2, 3, 4, 5 ]
}

foo(1, 2, 3, 4, 5)

数组的迭代方法 ( 遍历 )

​ 数组的遍历应该是前端接触数组用的最多的场景,基本上遇到数组很难避免遍历,所以这里我抽出来,单独放一个模块。

keys | values | entries

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let arr = [1, 2, 3, 4, 5]

for (const item of arr.keys()) {
console.log(item);
//0 1 2 3 4 侧面验证了数组就是一个特殊的对象 他的key就是下标
}

for (const item of arr.values()) {
console.log(item);
//1 2 3 4 5 返回的是 value
}

for (const item of arr.entries()) {
console.log(item);
// [ 0, 1 ] [ 1, 2 ] [ 2, 3 ] [ 3, 4 ] [ 4, 5 ]
// 返回的是键值对 key == value的形式
}

forEach

  1. forEach没有返回值,即便指定返回值,也是返回的undefined

  2. forEach有两个参数,第一个参数是回调函数即为每个元素执行怎样的操作,第二个可选参数是this指向,函数内部的this指向什么。回调函数有三个参数,第一个代表当前项,第二个是下标,第三个则是原数组,就是遍历的那个数组

  3. forEach无法被打断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let arr = [1, 2, 3, 4, 5]
    arr.forEach(item => {
    console.log(item);
    // 1 0 [ 1, 2, 3, 4, 5 ]
    // 2 1 [ 1, 2, 3, 4, 5 ]
    // 3 2 [ 1, 2, 3, 4, 5 ]
    // 4 3 [ 1, 2, 3, 4, 5 ]
    // 5 4 [ 1, 2, 3, 4, 5 ]
    });

tips:下面几个方法如果谈到第二个参数用来绑定this指向的话,一定要注意不要写成箭头函数,箭头函数是无法绑定this的,这一点需要敏感

map

  1. map和forEach的使用高度相似,forEach是没有返回值的,但是map是拥有返回值的,可以简单理解为:我们给定一个函数,使用map方法后会将数组的每一项走执行一次这个函数,并且返回一个数组,新数组和旧数组呈印射关系
  2. map有两个参数,第一个参数是回调函数即为每个元素执行怎样的操作,第二个可选参数是this指向,函数内部的this指向什么。回调函数有三个参数,第一个代表当前项,第二个是下标,第三个则是原数组,就是遍历的那个数组,这三个参数和forEach保持一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let arr = [1, 2, 3, 4, 5]

arr.map((item, index, arr) => {
console.log(item, index, arr);
// 1 0 [ 1, 2, 3, 4, 5 ]
// 2 1 [ 1, 2, 3, 4, 5 ]
// 3 2 [ 1, 2, 3, 4, 5 ]
// 4 3 [ 1, 2, 3, 4, 5 ]
// 5 4 [ 1, 2, 3, 4, 5 ]
// 可以看到三个参数和forEach一致
})

const newArr = arr.map((item, index, arr) => item * 2)
console.log(arr); // [ 1, 2, 3, 4, 5 ]
console.log(newArr); // [ 2, 4, 6, 8, 10 ]

filter

​ filter过滤函数,如果我们需要对数组的元素进行过滤处理,那么肯定优先考虑filter函数,它的返回值应该是一个布尔值,通过这个布尔值来决定要不要过滤当前项

  1. 参数和map、forEach方法一致,不多赘述了
  2. 区别就在于返回值,简单理解为:将数组中满足条件的项返回出去,并且组成一个新的数组
1
2
3
4
let arr = [1, 2, 3, 4, 5]
const newArr = arr.filter(item => item > 2) //返回数组中大于2的项
console.log(arr); // [ 1, 2, 3, 4, 5 ]
console.log(newArr); // [ 3, 4, 5 ]

some | every

​ some 用于判断数组有没有存在符合预期的项,有一项就行,evert 需要数组中所有项都满足条件。他们的返回值都是布尔值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const arr = [1, 2, 3, 4, 5]
let res1 = arr.some((item, index, arr) => {
console.log(item, index, arr);
// 1 0 [ 1, 2, 3, 4, 5 ]
// 2 1 [ 1, 2, 3, 4, 5 ]
// 3 2 [ 1, 2, 3, 4, 5 ]
return item > 2
})
// 可以发现some方法在找到符合预期的值,直接就返回了,不再继续往后面执行。
let res2 = arr.every((item, index, arr) => {
console.log(item, index, arr);
// 1 0 [ 1, 2, 3, 4, 5 ]
return item > 2
})
// every 一旦遇到不符合条件的值也是直接退出,因为该方法需要数组所有项都满足条件,有一个不满足就没必要继续执行了,浪费性能
console.log(res1);
console.log(res2);

万金油方法 reduce

​ 我们使用灵活使用reduce方法可以做出各种各样的骚操作。比如说MDN对map方法的示例。当然最经典的就是求和

  1. 参数: 第一个参数是回调函数,第二个是默认值,会作为第一次循环的时候回调函数的第一个参数
  2. 注意:如果不给默认值,就会少执行一次循环。数组的第一项直接作为回调函数的第一个参数,第二个参数从第二项开始

求和

1
2
3
let arr = [1, 2, 3, 4, 5]
let sum = arr.reduce((pre, cur) => pre + cur)
console.log(sum); // 15

这里第一次循环的时候,由于没有给出默认值,所以pre的值是数组的第一项,也就是1,cur是数组的第二项,说白了跳过循环第一项了。如果要避免这种情况,可以给个默认值0,这样cur就是从数组的第一项开始了

对象中的某个字段进行求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let arr = [
{
name: "coderwei1",
age: 1
},
{
name: "coderwei2",
age: 2
},
{
name: "coderwei3",
age: 3
},
]
let sumAge = arr.reduce((pre, cur) => pre + cur.age, 0)
console.log(sumAge); //6

这里就一定要给默认值,不然的话第一次循环pre拿到的是一个对象,求和之后就会出问题