{T}

数组方法

JavaScript 数组提供了丰富的内置方法,本章将系统介绍所有数组实例方法的用法、参数、返回值和最佳实践。

本章概览

本章是数组实例方法的完整参考手册,涵盖所有常用的数组操作方法。

code
数组方法体系
├── 基本操作方法
│   ├── 栈方法 - push, pop
│   ├── 队列方法 - shift, unshift
│   ├── 操作方法 - concat, slice, splice
│   └── 填充方法 - fill, copyWithin
├── 搜索排序方法
│   ├── 搜索方法 - indexOf, lastIndexOf, includes
│   ├── 查找方法 - find, findIndex
│   └── 排序方法 - sort, reverse
├── 迭代方法
│   ├── 遍历方法 - forEach
│   ├── 转换方法 - map, filter, flat, flatMap
│   ├── 归约方法 - reduce, reduceRight
│   └── 检测方法 - every, some
├── 转换方法
│   └── 字符串转换 - join, toString, toLocaleString
└── ES6+ 新增方法
    ├── 迭代器 - keys, values, entries
    └── 其他 - at, Array.from, Array.of

相关章节导航

方法分类总览

JavaScript 数组提供了超过 30 种内置方法,根据功能可以分为以下几大类:

// ... 中间省略 ...

  1. splice(index, howmany, item1, ..., itemX) - 添加/删除元素,返回被删除的元素数组。该方法会改变原数组

push、pop

push()pop() 方法实现了(LIFO - Last In First Out)数据结构的行为,在数组末尾进行添加和删除操作。

code
let colors = new Array() // 创建一个数组
let count = colors.push("red", "green") // 推入两项
console.log(count) // 2

count = colors.push("black") // 再推入一项
console.log(count) // 3

let item = colors.pop() // 取得最后一项
console.log(item) // black
console.log(colors.length) // 2

💡 栈的应用场景: - 函数调用栈

  • 撤销/重做操作
  • 表达式求值
  • 浏览器历史记录

shift、unshift

shift()unshift() 方法在数组开头进行操作。shift()push() 组合可以实现队列(FIFO - First In First Out)数据结构。

javascript
let colors = new Array()
let count = colors.push("red", "green")
console.log(count) // 2
count = colors.push("black")
console.log(count) // 3

let item = colors.shift() // 取得第一项
console.log(item) // red
console.log(colors.length) // 2

💡 队列的应用场景: - 任务调度

  • 消息队列
  • 广度优先搜索(BFS)
  • 打印队列 通过使用 unshiftpop,可以在相反方向上模拟队列,即在数组开头添加新数据,在数组末尾取得数据,如下例所示:
javascript
let colors = new Array()
let count = colors.unshift("red", "green") // 从数组开头推入两项
console.log(count) // 2
count = colors.unshift("black") // 再推入一项
console.log(count) // 3
let item = colors.pop() // 取得最后一项
console.log(item) // green
console.log(colors.length) // 2

concat

示例:数组的concat操作

javascript
let colors = ["red", "green", "blue"]
let colors2 = colors.concat("yellow", ["black", "brown"])
console.log(colors) // ["red", "green","blue"]
console.log(colors2) // ["red", "green", "blue", "yellow", "black", "brown"]

打平数组参数的行为可以重写,方法是在参数数组上指定一个特殊的符号:Symbol.isConcatSpreadable。这个符号能够阻止 concat 打平参数数组。相反把这个值设置为 true 可以强制打平类数组对象:

javascript
let colors = ["red", "green", "blue"]
let newColors = ["black", "brown"]

let moreNewColors = {
  [Symbol.isConcatSpreadable]: true,
  length: 2,
  0: "pink",
  1: "cyan"
}
newColors[Symbol.isConcatSpreadable] = false

// 强制不打平数组
let colors2 = colors.concat("yellow", newColors)

// 强制打平类数组对象
let colors3 = colors.concat(moreNewColors)

console.log(colors) // ["red", "green", "blue"]
console.log(colors2)
/**
[
  'red',
  'green',
  'blue',
  'yellow',
  [ 'black', 'brown', [Symbol(Symbol.isConcatSpreadable)]: false ]
]
*/
console.log(colors3) // ["red", "green", "blue", "pink", "cyan"]

slice

slice() 方法用于提取数组的一部分,返回一个新数组,不会修改原数组。支持负数索引。

javascript
let colors = ["red", "green", "blue", "yellow", "purple"]
let colors2 = colors.slice(1) // 从索引 1 到末尾
let colors3 = colors.slice(1, 4) // 从索引 1 到 4(不包括 4)
console.log(colors2) // ['green', 'blue', 'yellow', 'purple']
console.log(colors3) // ['green', 'blue', 'yellow']

// 负数索引(从末尾开始计算)
let colors4 = colors.slice(-3) // 最后 3 个元素
console.log(colors4) // ['blue', 'yellow', 'purple']

let colors5 = colors.slice(-3, -1) // 倒数第 3 个到倒数第 1 个(不包括)
console.log(colors5) // ['blue', 'yellow']

// 浅拷贝整个数组
let colorsCopy = colors.slice()
console.log(colorsCopy === colors) // false(新数组)

💡 slice() 常用于: - 数组浅拷贝:arr.slice()

  • 提取子数组
  • 处理数组的部分元素而不修改原数组

splice

splice 方法是数组中的最强大的方法 arrayObject.splice(index, howmany, item1,.....,itemX)

  • index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置
  • howmany:必需。要删除的项目数量。如果设置为 0,则不会删除项目
  • item1, ..., itemX:可选。向数组添加的新项目

从上面参数可知,splice 主要有三种使用形式:

  • 删除:需要给 splice 传递两个参数,即要删除的第一个元素的位置和要删除的元素的数量
  • 插入:需要给 splice 传递至少三个参数,即开始位置、0(要删除的元素数量)、要插入的元素
  • 替换:splice 方法可以在删除元素的同时在指定位置插入新的元素。同样需要传入至少三个参数,即开始位置、要删除的元素数量、要插入的元素。要插入的元素数量是任意的,不一定和删除的元素数量相等
javascript
let colors = ["red", "green", "blue"]
let removed = colors.splice(0, 1) // 删除第一项
console.log(colors) // green,blue
console.log(removed) // red,只有一个元素的数组

removed = colors.splice(1, 0, "yellow", "orange") // 在位置 1 插入两个元素
console.log(colors) // green,yellow,orange,blue
console.log(removed) // 空数组

removed = colors.splice(1, 1, "red", "purple") // 插入两个值,删除一个元素
console.log(colors) // green,red,purple,orange,blue
console.log(removed) // yellow,只有一个元素的数组

搜索和排序方法

  1. indexOf() - 返回数组中第一个匹配元素的索引
  2. lastIndexOf() - 返回数组中最后一个匹配元素的索引
  3. includes() - 判断数组是否包含某元素,返回布尔值
  4. find(callback) - 返回满足条件的第一个元素
  5. findIndex(callback) - 返回满足条件的第一个元素的索引
  6. sort([compareFunction]) - 对数组元素进行排序
  7. reverse() - 反转数组元素的顺序。该方法会改变原来的数组,而不会创建新的数组

indexOf、lastIndexOf、includes

ECMAScript 提供了 3 个严格相等的搜索方法:indexOflastIndexOfincludes。这些方法都接收两个参数:要查找的元素和可选的起始搜索位置(fromIndex

  • lastIndexOf 方法会从数组结尾元素开始向前搜索
  • indexOflastIndexOf 返回的是查找元素在数组中的索引值,如果没有找到,则返回 -1
  • includes 方法会返回布尔值,表示是否找到至少一个与指定元素匹配的项。在比较第一个参数和数组的每一项时,会使用全 === 比较,也就是说两项必须严格相等
javascript
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]
console.log(numbers.indexOf(4)) // 3
console.log(numbers.lastIndexOf(4)) // 5
console.log(numbers.includes(4)) // true
console.log(numbers.indexOf(4, 4)) // 5
console.log(numbers.lastIndexOf(4, 4)) // 3
console.log(numbers.includes(4, 7)) // false

let person = { name: "Nicholas" }
let people = [{ name: "Nicholas" }]
let morePeople = [person]
console.log(people.indexOf(person)) // -1
console.log(morePeople.indexOf(person)) // 0
console.log(people.includes(person)) // false
console.log(morePeople.includes(person)) // true

find、findIndex

findfindIndex 方法允许按照定义的断言函数搜索数组,每个索引都会调用这个函数。断言函数的返回值决定相应索引的元素是否被认为匹配。断言函数接收 3 个参数:

  • 元素:元素是数组中当前搜索的元素
  • 索引:索引是当前元素的索引
  • 数组本身:而数组就是正在搜索的数组

find 返回第一个匹配的元素,findIndex 返回第一个匹配元素的索引。这两个方法也都接收第二个可选的参数,用于指定断言函数内部 this 的值

javascript
const people = [
  {
    name: "Matt",
    age: 27
  },
  {
    name: "Nicholas",
    age: 29
  }
]

console.log(people.find((element, index, array) => element.age < 28)) // {name: "Matt", age: 27}

console.log(people.findIndex((element, index, array) => element.age < 28)) // 0

找到匹配项后,这两个方法都不再继续搜索

javascript
const evens = [2, 4, 6]

// 找到匹配后,永远不会检查数组的最后一个元素
evens.find((element, index, array) => {
  console.log(element)
  console.log(index)
  console.log(array)
  return element === 4
})

// 2
// 0
// [2, 4, 6]
// 4
// 1
// [2, 4, 6]

reverse

示例:数组的reverse 操作

javascript
arrayObject.reverse()
let array = [1, 2, 3, 4, 5]
let array2 = array.reverse()

console.log(array) // [5,4,3,2,1]
console.log(array2 === array) // true

sort

默认情况下,sort 会按照升序重新排列数组元素,即最小的值在前面,最大的值在后面。为此,sort 会在每一项上调用 String 转型函数,然后比较字符串来决定顺序。即使数组的元素都是数值,也会先把数组转换为字符串再比较、排序

javascript
let values = [0, 1, 5, 10, 15]
values.sort()
console.log(values) // 0,1,10,15,5
javascript
function compare(value1, value2) {
  if (value1 < value2) {
    return -1
  } else if (value1 > value2) {
    return 1
  } else {
    return 0
  }
}

let values = [0, 1, 5, 10, 15]
values.sort(compare)
console.log(values) // 0,1,5,10,15

在给 sort 方法传入比较函数后,数组中的数值在排序后保持正确的顺序。当然,比较函数也可以产生降序效果,只要把返回值交换一下即可:

javascript
if (value1 < value2) {
  return 1
} else if (value1 > value2) {
  return -1
} else {
  return 0
}

let values = [0, 1, 5, 10, 15]
values.sort(compare)
console.log(values) // 15,10,5,1,0

比较函数还可简写为一个箭头函数:

javascript
let values = [0, 1, 5, 10, 15]

values.sort((a, b) => (a < b ? 1 : a > b ? -1 : 0))

console.log(values) // 15,10,5,1,0

如果数组的元素是数值,或者是其 valueOf 方法返回数值的对象(如 Date 对象),这个比较函数还可以写得更简单,因为这时可以直接用第二个值减去第一个值:

javascript
function compare(value1, value2) {
  return value2 - value1
}

比较函数就是要返回小于 0、0 和大于 0 的数值,因此减法操作完全可以满足要求

迭代方法

  1. forEach(callback) - 对数组每个元素执行回调函数。该方法没有返回值
  2. map(callback)- 对数组每个元素执行回调函数,返回新数组
  3. every(callback) - 检查数组所有元素是否都满足条件
  4. some(callback) - 检查数组中是否有元素满足条件
  5. filter(callback) - 返回满足条件的所有元素组成的新数组
  6. reduce(callback, initialValue) - 从左到右对数组元素执行累加器函数
  7. reduceRight(callback, initialValue) - 从右到左对数组元素执行累加器函数

forEach

示例:数组的forEach 操作。方法用于调用数组的每个元素,并将元素传递给回调函数

javascript
let arr = [1, 2, 3, 4, 5]
arr.forEach((item, index, arr) => {
  console.log(index + ":" + item)
})
/**
0:1
1:2
2:3
3:4
4:5
*/

该方法还可以有第二个参数,用来绑定回调函数内部 this 变量(回调函数不能是箭头函数,因为箭头函数没有 this)

javascript
let arr = [1, 2, 3, 4, 5]
let arr1 = [9, 8, 7, 6, 5]

arr.forEach(function (item, index, arr) {
  console.log(this[index]) //  9 8 7 6 5
}, arr1)

map

示例:数组的 map 操作

javascript
let arr = [1, 2, 3]

let arr1 = arr.map((item) => {
  return item + 1
})
console.log(arr1) // [2, 3, 4]

第二个参数用来绑定参数函数内部的 this 变量:

javascript
var arr = ["a", "b", "c"]

let arr1 = [1, 2].map(function (e) {
  return this[e]
}, arr)
console.log(arr1) // ['b', 'c']

该方法可以进行链式调用:

javascript
let arr = [1, 2, 3]

arr.map((item) => item + 1).map((item) => item + 1) // [3, 4, 5]

every、some

示例:数组的 everysome 操作

javascript
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]

let everyResult = numbers.every((item, index, array) => item > 2)
console.log(everyResult) // false

let someResult = numbers.some((item, index, array) => item > 2)
console.log(someResult) // true

filter

filter 用于过滤数组,满足条件的元素会被返回。它的参数是一个回调函数,所有数组元素依次执行该函数,返回结果为 true 的元素会被返回。该方法会返回一个新的数组,不会改变原数组

javascript
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]
let filterResult = numbers.filter((item, index, array) => item > 2)

console.log(filterResult) // 3,4,5,4,3

let arr = [1, 2, 3, 4, 5]
let arr1 = arr.filter((item) => item > 2)
console.log(arr1) // [3, 4, 5]

可以使用 filter 方法来移除数组中的 undefinednullNaN0false""(空字符串)等假值:

javascript
const array = [1, undefined, "hello", null, NaN, 0, false, "", 42]

// 方法 1:使用 filter 方法移除所有"假值"(繁琐)
const filteredArray1 = array.filter((item) => {
  return item !== undefined && item !== null && !isNaN(item) && item !== 0 && item !== false && item !== ""
})

// 方法 2:使用 Boolean 构造函数(简洁,但会过滤掉所有假值)
const filteredArray2 = array.filter(Boolean)
console.log(filteredArray2) // [1, "hello", 42]

// 方法 3:只过滤 null 和 undefined
const filteredArray3 = array.filter((item) => item != null)
console.log(filteredArray3) // [1, "hello", NaN, 0, false, "", 42]

上面这些数组方法之中,有不少返回的还是数组,所以可以链式使用

javascript
var users = [
  { name: "tom", email: "tom@example.com" },
  { name: "peter", email: "peter@example.com" }
]

users
  .map(function (user) {
    return user.email
  })
  .filter(function (email) {
    return /^t/.test(email)
  })
  .forEach(function (email) {
    console.log(email)
  })
// "tom@example.com"

reduce 和 reduceRight

reduce 方法对数组中的每个元素执行一个 reducer 函数(升序执行),将其结果汇总为单个返回值

arr.reduce(callback,[initialValue]) 方法参数:

  • callback (执行数组中每个值的函数,包含四个参数)
    • previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
    • currentValue (数组中当前被处理的元素)
    • index (当前元素在数组中的索引)
    • array (调用 reduce 的数组)
  • initialValue (作为第一次调用 callback 的第一个参数)
javascript
let arr = [1, 2, 3, 4]
let sum = arr.reduce((prev, cur, index, arr) => {
  console.log(prev, cur, index)
  return prev + cur
})
console.log(arr, sum)

输出结果如下:

javascript
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10

添加初始值:

javascript
let arr = [1, 2, 3, 4]
let sum = arr.reduce((prev, cur, index, arr) => {
  console.log(prev, cur, index)
  return prev + cur
}, 5)
console.log(arr, sum)

输出:

javascript
5 1 0
6 2 1
8 3 2
11 4 3
[1, 2, 3, 4] 15

reduceRight 该方法和的上面的 reduce 用法几乎一致,只是该方法是对数组进行倒序查找的

javascript
let arr = [1, 2, 3, 4]
let sum = arr.reduceRight((prev, cur, index, arr) => {
  console.log(prev, cur, index)
  return prev + cur
}, 5)
console.log(arr, sum)

输出:

javascript
5 4 3
9 3 2
12 2 1
14 1 0
[1, 2, 3, 4] 15

ES6+新增方法

  1. Array.from() - 从类数组或可迭代对象创建新数组
  2. Array.of() - 根据参数创建新数组
  3. fill(value, start, end) - 用固定值填充数组
  4. copyWithin(target, start, end) - 复制数组元素到数组内的其他位置
  5. entries() - 返回数组键值对的迭代器
  6. keys()- 返回数组键的迭代器
  7. values() - 返回数组值的迭代器
  8. flat(depth)- 将嵌套数组扁平化
  9. flatMap(callback) - 先映射每个元素,然后将结果扁平化

fill

fill 方法可以向一个已有数组中插入全部或部分相同的值,开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。如果是负值,则将从负值加上数组的长度而得到的值开始

  • value:必需。填充的值
  • start:可选。开始填充位置;如果为负数,从后面开始
  • end:可选。停止填充位置 (默认为 array.length)
javascript
const zeroes = [0, 0, 0, 0, 0]

// 用 5 填充整个数组
zeroes.fill(5)
console.log(zeroes) // [5, 5, 5, 5, 5]
zeroes.fill(0) // 重置

// 用 6 填充索引大于等于 3 的元素
zeroes.fill(6, 3)
console.log(zeroes) // [0, 0, 0, 6, 6]
zeroes.fill(0) // 重置

// 用 7 填充索引大于等于 1 且小于 3 的元素
zeroes.fill(7, 1, 3)
console.log(zeroes) // [0, 7, 7, 0, 0];
zeroes.fill(0) // 重置

// 用 8 填充索引大于等于 1 且小于 4 的元素
// (-4 + zeroes.length = 1)
// (-1 + zeroes.length = 4)
zeroes.fill(8, -4, -1)
console.log(zeroes) // [0, 8, 8, 8, 0];

fill 静默忽略超出数组边界、零长度及方向相反的索引范围:

javascript
const zeroes = [0, 0, 0, 0, 0]

// 索引过低,忽略
zeroes.fill(1, -10, -6)
console.log(zeroes) // [0, 0, 0, 0, 0]

// 索引过高,忽略
zeroes.fill(1, 10, 15)
console.log(zeroes) // [0, 0, 0, 0, 0]

// 索引反向,忽略
zeroes.fill(2, 4, 2)
console.log(zeroes) // [0, 0, 0, 0, 0]

// 索引部分可用,填充可用部分
zeroes.fill(4, 3, 10)
console.log(zeroes) // [0, 0, 0, 4, 4]

copyWithin

copyWithin 方法会按照指定范围来浅复制数组中的部分内容,然后将它插入到指定索引开始的位置,开始与结束索引的计算方法和 fill 方法一样。array.copyWithin(target, start, end)

  • target:必需。复制到指定目标索引位置
  • start:可选。元素复制的起始位置
  • end:可选。停止复制的索引位置 (默认为 array.length)。如果为负值,表示倒数
javascript
let ints,
  reset = () => (ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
reset()

// 从 ints 中复制索引 0 开始的内容,插入到索引 5 开始的位置
ints.copyWithin(5)
console.log(ints) // [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

reset()
// 从 ints 中复制索引 5 开始的内容,插入到索引 0 开始的位置
ints.copyWithin(0, 5)
console.log(ints) // [5, 6, 7, 8, 9, 5, 6, 7, 8, 9]

reset()
// 从 ints 中复制索引 0 开始到索引 3 结束的内容插入到索引 4 开始的位置
ints.copyWithin(4, 0, 3)
console.log(ints) // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
reset()

// JavaScript 引擎在插值前会完整复制范围内的值,因此复制期间不存在重写的风险
ints.copyWithin(2, 0, 6)
console.log(ints) // [0, 1, 0, 1, 2, 3, 4, 5, 8, 9]
reset()

// 支持负索引值,与 fill()相对于数组末尾计算正向索引的过程是一样的
ints.copyWithin(-4, -7, -3)
console.log(ints) // [0, 1, 2, 3, 4, 5, 3, 4, 5, 6]

copyWithin 静默忽略超出数组边界、零长度及方向相反的索引范围:

javascript
let ints,
  reset = () => (ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

reset()
// 索引过低,忽略
ints.copyWithin(1, -15, -12)
console.log(ints) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引过高,忽略
ints.copyWithin(1, 12, 15)
console.log(ints) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引反向,忽略
ints.copyWithin(2, 4, 2)
console.log(ints) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引部分可用,复制、填充可用部分
ints.copyWithin(4, 7, 10)
console.log(ints) // [0, 1, 2, 3, 7, 8, 9, 7, 8, 9];

keys、values 和 entries

ES6Array 的原型上暴露 3 个用于检索数组内容的方法:keys()values()entries()

  • keys 返回数组索引的迭代器
  • values 返回数组元素的迭代器
  • entries 返回索引/值对的迭代器
javascript
const a = ["foo", "bar", "baz", "qux"]

console.log(a.keys()) // Object [Array Iterator] {}
console.log(a.values()) // Object [Array Iterator] {}
console.log(a.entries()) // Object [Array Iterator] {}

// 因为这些方法都返回迭代器,所以可以将它们的内容,通过 Array.from()直接转换为数组实例
const aKeys = Array.from(a.keys())
const aValues = Array.from(a.values())
const aEntries = Array.from(a.entries())
console.log(aKeys) // [0, 1, 2, 3]
console.log(aValues) // ["foo", "bar", "baz", "qux"]
console.log(aEntries) // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]

使用 ES6 的解构可以非常容易地在循环中拆分键/值对:

javascript
const a = ["foo", "bar", "baz", "qux"]
for (const [idx, element] of a.entries()) {
  console.log(idx)
  console.log(element)
}
// 0、foo
// 1、bar
// 2、bax
// 3、qux

flat

flat() 是 ES2019 (ES10) 引入的数组方法,用于将嵌套数组"扁平化"(即减少数组的嵌套层级)。

语法: const newArray = arr.flat([depth])

参数:

  • depth(可选):指定要提取嵌套数组的深度,默认为 1
javascript
const arr1 = [1, 2, [3, 4]]
console.log(arr1.flat()) // [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]]
console.log(arr2.flat()) // [1, 2, 3, 4, [5, 6]]

指定深度参数:

javascript
const arr3 = [1, 2, [3, 4, [5, 6]]]
console.log(arr3.flat(2)) // [1, 2, 3, 4, 5, 6]

const deeplyNested = [1, [2, [3, [4, [5]]]]]
console.log(deeplyNested.flat(Infinity)) // [1, 2, 3, 4, 5]

移除空项:

javascript
const arr4 = [1, 2, , 4, 5]
console.log(arr4.flat()) // [1, 2, 4, 5]

注意事项:

  1. flat() 不会改变原数组,而是返回一个新数组
  2. 如果 depth 参数为 Infinity,会将所有嵌套数组完全扁平化
  3. flat() 会自动跳过数组中的空位(稀疏数组的空槽)

flat() 方法出现前的实现:

javascript
// 使用 reduce 和 concat 实现扁平化
const flatten = (arr) => arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val), [])

console.log(flatten([1, [2, [3]]])) // [1, 2, 3]

flatMap

flatMap() 是 ES2019 (ES10) 引入的数组方法,它是 map()flat(1) 的组合,先对数组的每个元素执行映射函数,然后将结果扁平化一级。

语法: const newArray = arr.flatMap(callback(currentValue[, index[, array]])[, thisArg])

参数:

  • callback:生成新数组元素的函数,接收三个参数:
    • currentValue:当前处理的元素
    • index(可选):当前元素的索引
    • array(可选):调用 flatMap 的数组
  • thisArg(可选):执行 callback 时用作 this 的值
javascript
const arr = [1, 2, 3, 4]

// 相当于 map + flat(1)
const result = arr.flatMap((x) => [x * 2])
console.log(result) // [2, 4, 6, 8]

对比 map() + flat()

javascript
const arr = [1, 2, 3]

// 使用 map + flat
const mappedThenFlattened = arr.map((x) => [x * 2]).flat()
console.log(mappedThenFlattened) // [2, 4, 6]

// 使用 flatMap(更高效,只遍历一次)
const flatMapped = arr.flatMap((x) => [x * 2])
console.log(flatMapped) // [2, 4, 6]

处理嵌套数组:

javascript
const sentences = ["Hello world", "Good morning", "JavaScript is fun"]

// 分割单词并扁平化
const words = sentences.flatMap((sentence) => sentence.split(" "))
console.log(words)
// ["Hello", "world", "Good", "morning", "JavaScript", "is", "fun"]

过滤元素(通过返回空数组):

javascript
const numbers = [1, 2, 3, 4, 5, 6]

// 过滤掉偶数
const oddNumbers = numbers.flatMap((num) => (num % 2 !== 0 ? [num] : []))
console.log(oddNumbers) // [1, 3, 5]

注意事项:

  1. flatMap() 只能扁平化一级嵌套,更深层次的嵌套不会被扁平化
  2. map() 不同,flatMap() 的回调函数必须返回一个数组
  3. flatMap() 不会改变原数组,而是返回一个新数组
  4. flatMap() 会自动跳过数组中的空位(稀疏数组的空槽)

处理 API 返回的嵌套数据:

javascript
const apiResponse = [
  { id: 1, tags: ["javascript", "web"] },
  { id: 2, tags: ["node", "backend"] }
]

const allTags = apiResponse.flatMap((item) => item.tags)
console.log(allTags) // ['javascript', 'web', 'node', 'backend']

其他实用方法

这些方法提供了数组的转换、检测和访问功能:

  1. join(separator) - 将数组元素连接为字符串
  2. toString() - 将数组转换为字符串
  3. Array.isArray() - 判断对象是否为数组(静态方法)
  4. at(index) - 返回指定索引处的元素(支持负数索引,ES2022)

join

join() 方法将数组元素连接为字符串。元素是通过指定的分隔符进行分隔的。

语法: arr.join([separator])

参数:

  • separator(可选):用来指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符

返回值: 一个字符串,通过把数组的每个元素转换为字符串,然后把这些字符串连接起来,在两个元素之间插入 separator 字符串而生成。

javascript
let colors = ["red", "green", "blue"]
console.log(colors.join(",")) // "red,green,blue"
console.log(colors.join("||")) // "red||green||blue"
console.log(colors.join()) // "red,green,blue"(默认逗号)
console.log(colors.join("")) // "redgreenblue"(无分隔符)
console.log(colors.join(" - ")) // "red - green - blue"

// 处理空值和 undefined
const arr = [1, null, undefined, 4]
console.log(arr.join("-")) // "1--4"(null 和 undefined 转为空字符串)

💡 join()split() 的逆操作,常用于: - 生成 CSV 格式数据

  • 构建 URL 查询字符串
  • 格式化输出

at

at() 方法是 ES2022 引入的,支持使用负数索引从数组末尾访问元素。

语法: arr.at(index)

参数:

  • index:要访问的元素的索引。支持负数,从数组末尾开始计算(-1 表示最后一个元素)

返回值: 指定索引处的元素,如果索引超出范围则返回 undefined

javascript
const arr = [1, 2, 3, 4, 5]

// 正数索引(从前往后)
console.log(arr.at(0)) // 1
console.log(arr.at(2)) // 3

// 负数索引(从后往前)
console.log(arr.at(-1)) // 5(最后一个元素)
console.log(arr.at(-2)) // 4(倒数第二个元素)

// 超出范围返回 undefined
console.log(arr.at(10)) // undefined
console.log(arr.at(-10)) // undefined

// 对比传统方式
console.log(arr[arr.length - 1]) // 5(需要计算长度)
console.log(arr.at(-1)) // 5(更简洁)

💡 at() 方法的优势: - 支持负数索引,访问末尾元素更方便

  • arr[arr.length - 1] 更简洁
  • 与字符串的 at() 方法行为一致

toLocaleString、toString、valueOf

数组的转化方法主要有四个:toLocaleStringtoStringvalueOfjoin

javascript
let colors = ["red", "blue", "green"] // 创建一个包含 3 个字符串的数组

console.log(colors.toString()) // red,blue,green
console.log(colors.valueOf()) // red,blue,green
console.log(colors) // red,blue,green

显式调用的 toStringvalueOf 方法,分别返回数组的字符串表示,即将所有字符串组合起来,以逗号分隔。最后一行代码直接用 console.log 显示数组,因为 console.log 期待字符串,所以会在后台调用数组的 toString 方法,从而得到跟前面一样的结果

javascript
let person1 = {
  toLocaleString() {
    return "Nikolaos"
  },
  toString() {
    return "Nicholas"
  }
}

let person2 = {
  toLocaleString() {
    return "Grigorios"
  },
  toString() {
    return "Greg"
  }
}

let people = [person1, person2]
console.log(people) // Nicholas,Greg 。这里默认调用的是 toString 方法
console.log(people.toString()) // Nicholas,Greg
console.log(people.toLocaleString()) // Nikolaos,Grigorios

方法总结

按是否改变原数组分类

了解哪些方法会改变原数组,哪些不会,对于编写正确的代码非常重要。

改变原数组的方法: 调用这些方法后,原始数组的内容或结构会被修改

不改变原数组的方法: 这些方法会返回一个新值或新数组,而不会修改原始数组

| 改变原数组的方法 | 不改变原数组的方法 |

// ... 中间省略 ...

  1. reduce 缺少初始值: 可能导致错误,特别是空数组时

方法选择指南

快速选择流程图

code
需要什么操作?
│
├─ 增删元素
│  ├─ 末尾操作 → push() / pop()
│  ├─ 开头操作 → shift() / unshift()
│  └─ 任意位置 → splice()
│
├─ 查找元素
│  ├─ 按值查找 → indexOf() / includes()
│  ├─ 按条件查找 → find() / findIndex()
│  └─ 是否存在 → some() / includes()
│
├─ 遍历转换
│  ├─ 只遍历 → forEach()
│  ├─ 转换数组 → map()
│  ├─ 过滤元素 → filter()
│  └─ 累积结果 → reduce()
│
├─ 检测条件
│  ├─ 所有满足 → every()
│  └─ 任一满足 → some()
│
├─ 排序反转
│  ├─ 排序 → sort()
│  └─ 反转 → reverse()
│
└─ 其他操作
   ├─ 截取 → slice()
   ├─ 合并 → concat()
   ├─ 扁平化 → flat() / flatMap()
   └─ 转字符串 → join()

场景化方法推荐

1. 数组操作场景

场景推荐方法示例
添加元素到末尾push()arr.push(item)
添加元素到开头unshift()arr.unshift(item)
删除末尾元素pop()arr.pop()
删除开头元素shift()arr.shift()
删除任意位置元素splice()arr.splice(index, 1)

// ... 中间省略 ...

| 合并数组 | concat()[...arr1, ...arr2] | arr1.concat(arr2) | | 截取子数组 | slice() | arr.slice(start, end) | | 数组复制 | slice()[...arr] | arr.slice() |

方法链式调用示例

code
// 示例1:数据管道处理
const products = [
  {name: 'Apple', price: 5, category: 'fruit'},
  {name: 'Carrot', price: 2, category: 'vegetable'},
  {name: 'Banana', price: 3, category: 'fruit'}
]

// 过滤水果 -> 按价格排序 -> 提取名称
const result = products
  .filter(p => p.category === 'fruit')
  .sort((a, b) => a.price - b.price)
  .map(p => p.name)

console.log(result) // ['Banana', 'Apple']

// 示例2:复杂数据聚合
const orders = [
  {product: 'Apple', quantity: 2, price: 5},
  {product: 'Banana', quantity: 3, price: 3},
  {product: 'Apple', quantity: 1, price: 5}
]

// 按产品分组并计算总销售额
const salesByProduct = orders
  .reduce((acc, order) => {
    const key = order.product
    acc[key] = (acc[key] || 0) + order.quantity * order.price
    return acc
  }, {})

console.log(salesByProduct) // {Apple: 15, Banana: 9}

性能对比

方法性能对比表

基于 100,000 个元素的数组测试(相对性能,数值越小越快):

操作最快方法中等方法最慢方法
遍历for 循环 (1.0x)for...of (1.1x)forEach (1.3x)
查找includes (1.0x)indexOf (1.1x)find (1.5x)
过滤for + push (1.0x)filter (1.4x)reduce (1.8x)
映射for 循环 (1.0x)map (1.2x)forEach + push (1.5x)
去重Set (1.0x)filter + indexOf (3.0x)双重循环 (10.0x+)

性能优化建议

javascript
// 1. 避免在循环中修改数组
// ❌ 差
for (let i = 0; i < arr.length; i++) {
  if (condition) {
    arr.splice(i, 1)  // 修改数组长度,影响性能
    i--
  }
}

// ✅ 好
const filtered = arr.filter(x => !condition)


  // ... 中间省略 ...


// 5. 缓存数组长度
// ✅ 在密集循环中
for (let i = 0, len = arr.length; i < len; i++) {
  // 处理逻辑
}

常见错误与解决方案

错误1:混淆 splice 和 slice

javascript
// ❌ 错误:想复制数组却用了 splice
const copy = arr.splice()  // 返回空数组,原数组不变

// ✅ 正确:使用 slice
const copy = arr.slice()   // 返回数组副本

// 记忆技巧:splice 有 'p'(破坏性的),slice 没有

错误2:sort() 的默认行为

javascript
// ❌ 错误:数字排序结果不正确
[10, 2, 30].sort()  // [10, 2, 30](按字符串排序)

// ✅ 正确:提供比较函数
[10, 2, 30].sort((a, b) => a - b)  // [2, 10, 30]

错误3:忘记 return 语句

javascript
// ❌ 错误:map 中忘记 return
const doubled = arr.map(x => {
  x * 2  // 忘记 return,结果全是 undefined
})

// ✅ 正确:显式 return 或使用箭头函数简写
const doubled = arr.map(x => x * 2)

错误4:forEach 无法中断

javascript
// ❌ 错误:想中断循环但 return 无效
arr.forEach(x => {
  if (x === target) {
    return  // 只是跳过当前迭代,不会中断循环
  }
  console.log(x)
})

// ✅ 正确:使用 for...of 或 some
for (const x of arr) {
  if (x === target) break
  console.log(x)
}

// 或使用 some
arr.some(x => {
  if (x === target) return true  // 中断
  console.log(x)
  return false
})

错误5:reduce 没有初始值

javascript
// ❌ 错误:空数组没有初始值会报错
[].reduce((a, b) => a + b)  // TypeError: Reduce of empty array with no initial value

// ✅ 正确:提供初始值
[].reduce((a, b) => a + b, 0)  // 0

// ✅ 正确:对象累积时必须提供初始值
const result = arr.reduce((acc, item) => {
  acc[item.id] = item.value
  return acc
}, {})  // 初始值为空对象

错误6:忽略数组方法的返回值

javascript
// ❌ 错误:filter 返回新数组,但赋值给了原变量
let arr = [1, 2, 3, 4, 5]
arr.filter(x => x > 2)  // 返回新数组,但没有接收
console.log(arr)  // [1, 2, 3, 4, 5](原数组未改变)

// ✅ 正确:接收返回值
arr = arr.filter(x => x > 2)
console.log(arr)  // [3, 4, 5]

API 速查表

增删改查方法

方法语法返回值改变原数组用途
pusharr.push(item)新长度末尾添加
poparr.pop()删除的元素删除末尾
unshiftarr.unshift(item)新长度开头添加
shiftarr.shift()删除的元素删除开头
splicearr.splice(start, deleteCount, ...items)删除的元素数组增删替换
slicearr.slice(start, end)新数组截取数组
concatarr.concat(...items)新数组合并数组

查找检测方法

方法语法返回值改变原数组用途
indexOfarr.indexOf(item, fromIndex)索引或 -1查找索引
lastIndexOfarr.lastIndexOf(item, fromIndex)索引或 -1从后查找
includesarr.includes(item, fromIndex)布尔值是否包含
findarr.find(callback)元素或 undefined按条件查找
findIndexarr.findIndex(callback)索引或 -1查找索引
everyarr.every(callback)布尔值全部满足?
somearr.some(callback)布尔值任一满足?

迭代方法

方法语法返回值改变原数组用途
forEacharr.forEach(callback)undefined遍历数组
maparr.map(callback)新数组映射转换
filterarr.filter(callback)新数组过滤元素
reducearr.reduce(callback, init)累积值归约计算
reduceRightarr.reduceRight(callback, init)累积值从右归约

排序转换方法

方法语法返回值改变原数组用途
sortarr.sort(compareFn)排序后的数组排序
reversearr.reverse()反转后的数组反转
joinarr.join(separator)字符串转字符串
toStringarr.toString()字符串转字符串

ES6+ 新增方法

方法语法返回值改变原数组用途
fillarr.fill(value, start, end)填充后的数组填充
copyWithinarr.copyWithin(target, start, end)修改后的数组内部复制
flatarr.flat(depth)新数组扁平化
flatMaparr.flatMap(callback)新数组映射+扁平化
keysarr.keys()迭代器获取键
valuesarr.values()迭代器获取值
entriesarr.entries()迭代器获取键值对
atarr.at(index)元素或 undefined按索引访问

小结

本章系统介绍了 JavaScript 数组的所有实例方法:

  1. 基本操作:增删元素、截取合并、填充复制
  2. 搜索排序:查找元素、条件检测、排序反转
  3. 迭代处理:遍历映射、过滤归约、扁平化处理
  4. 转换方法:字符串转换、迭代器方法
  5. ES6+ 新特性:flat、flatMap、at 等

关键要点:

  • ✅ 了解哪些方法会改变原数组,哪些不会
  • ✅ 选择合适的方法,平衡性能和可读性
  • ✅ 避免常见错误,如 sort() 默认行为、forEach 无法中断等
  • ✅ 善用方法链式调用,但注意性能影响

下一章预告:在数组常见操作中,我们将学习数组操作的实战技巧,包括去重、扁平化、深浅拷贝、乱序、分组等常用场景。


💡 学习建议:数组方法是 JavaScript 开发中最常用的工具,建议多练习、多实践。可以通过在线练习平台(如 LeetCode)来巩固这些方法的使用。