数组方法
JavaScript 数组提供了丰富的内置方法,本章将系统介绍所有数组实例方法的用法、参数、返回值和最佳实践。
本章概览
本章是数组实例方法的完整参考手册,涵盖所有常用的数组操作方法。
数组方法体系
├── 基本操作方法
│ ├── 栈方法 - 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 种内置方法,根据功能可以分为以下几大类:
// ... 中间省略 ...
splice(index, howmany, item1, ..., itemX)- 添加/删除元素,返回被删除的元素数组。该方法会改变原数组
push、pop
push() 和 pop() 方法实现了栈(LIFO - Last In First Out)数据结构的行为,在数组末尾进行添加和删除操作。
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)数据结构。
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)
- 打印队列 通过使用
unshift和pop,可以在相反方向上模拟队列,即在数组开头添加新数据,在数组末尾取得数据,如下例所示:
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) // 2concat
示例:数组的concat操作
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 可以强制打平类数组对象:
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() 方法用于提取数组的一部分,返回一个新数组,不会修改原数组。支持负数索引。
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方法可以在删除元素的同时在指定位置插入新的元素。同样需要传入至少三个参数,即开始位置、要删除的元素数量、要插入的元素。要插入的元素数量是任意的,不一定和删除的元素数量相等
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,只有一个元素的数组搜索和排序方法
indexOf()- 返回数组中第一个匹配元素的索引lastIndexOf()- 返回数组中最后一个匹配元素的索引includes()- 判断数组是否包含某元素,返回布尔值find(callback)- 返回满足条件的第一个元素findIndex(callback)- 返回满足条件的第一个元素的索引sort([compareFunction])- 对数组元素进行排序reverse()- 反转数组元素的顺序。该方法会改变原来的数组,而不会创建新的数组
indexOf、lastIndexOf、includes
ECMAScript 提供了 3 个严格相等的搜索方法:indexOf、lastIndexOf、includes。这些方法都接收两个参数:要查找的元素和可选的起始搜索位置(fromIndex)
lastIndexOf方法会从数组结尾元素开始向前搜索indexOf和lastIndexOf返回的是查找元素在数组中的索引值,如果没有找到,则返回-1includes方法会返回布尔值,表示是否找到至少一个与指定元素匹配的项。在比较第一个参数和数组的每一项时,会使用全===比较,也就是说两项必须严格相等
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)) // truefind、findIndex
find 和 findIndex 方法允许按照定义的断言函数搜索数组,每个索引都会调用这个函数。断言函数的返回值决定相应索引的元素是否被认为匹配。断言函数接收 3 个参数:
- 元素:元素是数组中当前搜索的元素
- 索引:索引是当前元素的索引
- 数组本身:而数组就是正在搜索的数组
find 返回第一个匹配的元素,findIndex 返回第一个匹配元素的索引。这两个方法也都接收第二个可选的参数,用于指定断言函数内部 this 的值
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找到匹配项后,这两个方法都不再继续搜索
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 操作
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) // truesort
默认情况下,sort 会按照升序重新排列数组元素,即最小的值在前面,最大的值在后面。为此,sort 会在每一项上调用 String 转型函数,然后比较字符串来决定顺序。即使数组的元素都是数值,也会先把数组转换为字符串再比较、排序
let values = [0, 1, 5, 10, 15]
values.sort()
console.log(values) // 0,1,10,15,5function 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 方法传入比较函数后,数组中的数值在排序后保持正确的顺序。当然,比较函数也可以产生降序效果,只要把返回值交换一下即可:
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比较函数还可简写为一个箭头函数:
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 对象),这个比较函数还可以写得更简单,因为这时可以直接用第二个值减去第一个值:
function compare(value1, value2) {
return value2 - value1
}比较函数就是要返回小于 0、0 和大于 0 的数值,因此减法操作完全可以满足要求
迭代方法
forEach(callback)- 对数组每个元素执行回调函数。该方法没有返回值map(callback)- 对数组每个元素执行回调函数,返回新数组every(callback)- 检查数组所有元素是否都满足条件some(callback)- 检查数组中是否有元素满足条件filter(callback)- 返回满足条件的所有元素组成的新数组reduce(callback, initialValue)- 从左到右对数组元素执行累加器函数reduceRight(callback, initialValue)- 从右到左对数组元素执行累加器函数
forEach
示例:数组的forEach 操作。方法用于调用数组的每个元素,并将元素传递给回调函数
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)
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 操作
let arr = [1, 2, 3]
let arr1 = arr.map((item) => {
return item + 1
})
console.log(arr1) // [2, 3, 4]第二个参数用来绑定参数函数内部的 this 变量:
var arr = ["a", "b", "c"]
let arr1 = [1, 2].map(function (e) {
return this[e]
}, arr)
console.log(arr1) // ['b', 'c']该方法可以进行链式调用:
let arr = [1, 2, 3]
arr.map((item) => item + 1).map((item) => item + 1) // [3, 4, 5]every、some
示例:数组的 every、some 操作
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) // truefilter
filter 用于过滤数组,满足条件的元素会被返回。它的参数是一个回调函数,所有数组元素依次执行该函数,返回结果为 true 的元素会被返回。该方法会返回一个新的数组,不会改变原数组
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 方法来移除数组中的 undefined、null、NaN、0、false、""(空字符串)等假值:
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]上面这些数组方法之中,有不少返回的还是数组,所以可以链式使用
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的第一个参数)
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)输出结果如下:
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10添加初始值:
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)输出:
5 1 0
6 2 1
8 3 2
11 4 3
[1, 2, 3, 4] 15reduceRight 该方法和的上面的 reduce 用法几乎一致,只是该方法是对数组进行倒序查找的
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)输出:
5 4 3
9 3 2
12 2 1
14 1 0
[1, 2, 3, 4] 15ES6+新增方法
Array.from()- 从类数组或可迭代对象创建新数组Array.of()- 根据参数创建新数组fill(value, start, end)- 用固定值填充数组copyWithin(target, start, end)- 复制数组元素到数组内的其他位置entries()- 返回数组键值对的迭代器keys()- 返回数组键的迭代器values()- 返回数组值的迭代器flat(depth)- 将嵌套数组扁平化flatMap(callback)- 先映射每个元素,然后将结果扁平化
fill
fill 方法可以向一个已有数组中插入全部或部分相同的值,开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。如果是负值,则将从负值加上数组的长度而得到的值开始
value:必需。填充的值start:可选。开始填充位置;如果为负数,从后面开始end:可选。停止填充位置 (默认为array.length)
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 静默忽略超出数组边界、零长度及方向相反的索引范围:
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)。如果为负值,表示倒数
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 静默忽略超出数组边界、零长度及方向相反的索引范围:
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
在 ES6 中 Array 的原型上暴露 3 个用于检索数组内容的方法:keys()、values() 和 entries():
keys返回数组索引的迭代器values返回数组元素的迭代器entries返回索引/值对的迭代器
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 的解构可以非常容易地在循环中拆分键/值对:
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、quxflat
flat() 是 ES2019 (ES10) 引入的数组方法,用于将嵌套数组"扁平化"(即减少数组的嵌套层级)。
语法: const newArray = arr.flat([depth])
参数:
depth(可选):指定要提取嵌套数组的深度,默认为 1
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]]指定深度参数:
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]移除空项:
const arr4 = [1, 2, , 4, 5]
console.log(arr4.flat()) // [1, 2, 4, 5]注意事项:
flat()不会改变原数组,而是返回一个新数组- 如果
depth参数为Infinity,会将所有嵌套数组完全扁平化 flat()会自动跳过数组中的空位(稀疏数组的空槽)
在 flat() 方法出现前的实现:
// 使用 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的值
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():
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]处理嵌套数组:
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"]过滤元素(通过返回空数组):
const numbers = [1, 2, 3, 4, 5, 6]
// 过滤掉偶数
const oddNumbers = numbers.flatMap((num) => (num % 2 !== 0 ? [num] : []))
console.log(oddNumbers) // [1, 3, 5]注意事项:
flatMap()只能扁平化一级嵌套,更深层次的嵌套不会被扁平化- 与
map()不同,flatMap()的回调函数必须返回一个数组 flatMap()不会改变原数组,而是返回一个新数组flatMap()会自动跳过数组中的空位(稀疏数组的空槽)
处理 API 返回的嵌套数据:
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']其他实用方法
这些方法提供了数组的转换、检测和访问功能:
join(separator)- 将数组元素连接为字符串toString()- 将数组转换为字符串Array.isArray()- 判断对象是否为数组(静态方法)at(index)- 返回指定索引处的元素(支持负数索引,ES2022)
join
join() 方法将数组元素连接为字符串。元素是通过指定的分隔符进行分隔的。
语法: arr.join([separator])
参数:
separator(可选):用来指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符
返回值: 一个字符串,通过把数组的每个元素转换为字符串,然后把这些字符串连接起来,在两个元素之间插入 separator 字符串而生成。
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
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
数组的转化方法主要有四个:toLocaleString、toString、valueOf、join
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显式调用的 toString 和 valueOf 方法,分别返回数组的字符串表示,即将所有字符串组合起来,以逗号分隔。最后一行代码直接用 console.log 显示数组,因为 console.log 期待字符串,所以会在后台调用数组的 toString 方法,从而得到跟前面一样的结果
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方法总结
按是否改变原数组分类
了解哪些方法会改变原数组,哪些不会,对于编写正确的代码非常重要。
改变原数组的方法: 调用这些方法后,原始数组的内容或结构会被修改
不改变原数组的方法: 这些方法会返回一个新值或新数组,而不会修改原始数组
| 改变原数组的方法 | 不改变原数组的方法 |
// ... 中间省略 ...
reduce缺少初始值: 可能导致错误,特别是空数组时
方法选择指南
快速选择流程图
需要什么操作?
│
├─ 增删元素
│ ├─ 末尾操作 → 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() |
方法链式调用示例
// 示例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+) |
性能优化建议
// 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
// ❌ 错误:想复制数组却用了 splice
const copy = arr.splice() // 返回空数组,原数组不变
// ✅ 正确:使用 slice
const copy = arr.slice() // 返回数组副本
// 记忆技巧:splice 有 'p'(破坏性的),slice 没有错误2:sort() 的默认行为
// ❌ 错误:数字排序结果不正确
[10, 2, 30].sort() // [10, 2, 30](按字符串排序)
// ✅ 正确:提供比较函数
[10, 2, 30].sort((a, b) => a - b) // [2, 10, 30]错误3:忘记 return 语句
// ❌ 错误:map 中忘记 return
const doubled = arr.map(x => {
x * 2 // 忘记 return,结果全是 undefined
})
// ✅ 正确:显式 return 或使用箭头函数简写
const doubled = arr.map(x => x * 2)错误4:forEach 无法中断
// ❌ 错误:想中断循环但 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 没有初始值
// ❌ 错误:空数组没有初始值会报错
[].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:忽略数组方法的返回值
// ❌ 错误: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 速查表
增删改查方法
| 方法 | 语法 | 返回值 | 改变原数组 | 用途 |
|---|---|---|---|---|
push | arr.push(item) | 新长度 | ✅ | 末尾添加 |
pop | arr.pop() | 删除的元素 | ✅ | 删除末尾 |
unshift | arr.unshift(item) | 新长度 | ✅ | 开头添加 |
shift | arr.shift() | 删除的元素 | ✅ | 删除开头 |
splice | arr.splice(start, deleteCount, ...items) | 删除的元素数组 | ✅ | 增删替换 |
slice | arr.slice(start, end) | 新数组 | ❌ | 截取数组 |
concat | arr.concat(...items) | 新数组 | ❌ | 合并数组 |
查找检测方法
| 方法 | 语法 | 返回值 | 改变原数组 | 用途 |
|---|---|---|---|---|
indexOf | arr.indexOf(item, fromIndex) | 索引或 -1 | ❌ | 查找索引 |
lastIndexOf | arr.lastIndexOf(item, fromIndex) | 索引或 -1 | ❌ | 从后查找 |
includes | arr.includes(item, fromIndex) | 布尔值 | ❌ | 是否包含 |
find | arr.find(callback) | 元素或 undefined | ❌ | 按条件查找 |
findIndex | arr.findIndex(callback) | 索引或 -1 | ❌ | 查找索引 |
every | arr.every(callback) | 布尔值 | ❌ | 全部满足? |
some | arr.some(callback) | 布尔值 | ❌ | 任一满足? |
迭代方法
| 方法 | 语法 | 返回值 | 改变原数组 | 用途 |
|---|---|---|---|---|
forEach | arr.forEach(callback) | undefined | ❌ | 遍历数组 |
map | arr.map(callback) | 新数组 | ❌ | 映射转换 |
filter | arr.filter(callback) | 新数组 | ❌ | 过滤元素 |
reduce | arr.reduce(callback, init) | 累积值 | ❌ | 归约计算 |
reduceRight | arr.reduceRight(callback, init) | 累积值 | ❌ | 从右归约 |
排序转换方法
| 方法 | 语法 | 返回值 | 改变原数组 | 用途 |
|---|---|---|---|---|
sort | arr.sort(compareFn) | 排序后的数组 | ✅ | 排序 |
reverse | arr.reverse() | 反转后的数组 | ✅ | 反转 |
join | arr.join(separator) | 字符串 | ❌ | 转字符串 |
toString | arr.toString() | 字符串 | ❌ | 转字符串 |
ES6+ 新增方法
| 方法 | 语法 | 返回值 | 改变原数组 | 用途 |
|---|---|---|---|---|
fill | arr.fill(value, start, end) | 填充后的数组 | ✅ | 填充 |
copyWithin | arr.copyWithin(target, start, end) | 修改后的数组 | ✅ | 内部复制 |
flat | arr.flat(depth) | 新数组 | ❌ | 扁平化 |
flatMap | arr.flatMap(callback) | 新数组 | ❌ | 映射+扁平化 |
keys | arr.keys() | 迭代器 | ❌ | 获取键 |
values | arr.values() | 迭代器 | ❌ | 获取值 |
entries | arr.entries() | 迭代器 | ❌ | 获取键值对 |
at | arr.at(index) | 元素或 undefined | ❌ | 按索引访问 |
小结
本章系统介绍了 JavaScript 数组的所有实例方法:
- 基本操作:增删元素、截取合并、填充复制
- 搜索排序:查找元素、条件检测、排序反转
- 迭代处理:遍历映射、过滤归约、扁平化处理
- 转换方法:字符串转换、迭代器方法
- ES6+ 新特性:flat、flatMap、at 等
关键要点:
- ✅ 了解哪些方法会改变原数组,哪些不会
- ✅ 选择合适的方法,平衡性能和可读性
- ✅ 避免常见错误,如 sort() 默认行为、forEach 无法中断等
- ✅ 善用方法链式调用,但注意性能影响
下一章预告:在数组常见操作中,我们将学习数组操作的实战技巧,包括去重、扁平化、深浅拷贝、乱序、分组等常用场景。
💡 学习建议:数组方法是 JavaScript 开发中最常用的工具,建议多练习、多实践。可以通过在线练习平台(如 LeetCode)来巩固这些方法的使用。