技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

超级详细的 JS 数组方法详解

所属馆藏
JavaScript
文件格式
Markdown
原始路径
JavaScript/125-超级详细的 js数组方法详解
本文目录11 个章节
  1. 1. 使用数组字面量
  2. 2. 使用 Array 构造函数
  3. 3. Array.of(ES2015)
  4. 4. Array.from(ES2015)
  5. 数组原型方法概览
  6. 各个方法的基本功能详解
  7. 1. at()
  8. 2. findLast() 和 findLastIndex()
  9. 3. 不修改原数组的复制方法
  10. 4. Array.fromAsync()
  11. 5. 选择方法时的建议

超级详细的 JS 数组方法详解

Category(分类): JavaScript Status: 未知

数组是 JavaScript 中最常用的数据集合。熟练掌握数组方法,可以提高代码可读性和工作效率;但还要关注方法是否修改原数组、是否跳过稀疏数组空位,以及回调函数的参数和返回值。

一、创建数组

1. 使用数组字面量

数组字面量是最直观、最常用的写法:

const arr4 = []
const arr5 = [20]
const arr6 = ['lily', 'lucy', 'Tom']

console.log(arr4.length) // 0
console.log(arr5.length) // 1,而不是“长度为 20”
console.log(arr6.length) // 3

2. 使用 Array 构造函数

无参构造

const arr1 = new Array()
console.log(arr1) // []

带参构造

new Array(length) 只有在恰好传入一个非负整数时才表示创建指定长度的稀疏数组,数组中并没有对应数量的实际元素。传入多个参数,或传入一个非数字参数,则会把参数作为元素:

const arr2 = new Array(20)
console.log(arr2.length) // 20
console.log(0 in arr2) // false,索引 0 是空位

const arr3 = new Array('lily', 'lucy', 'Tom')
const arr4 = new Array('23')
console.log(arr3) // ['lily', 'lucy', 'Tom']
console.log(arr4) // ['23']

日常代码中通常优先使用数组字面量,避免 new Array(value) 在单参数场景下产生歧义。

3. Array.of(ES2015)

Array.of() 总会把所有参数作为数组元素,不会把单个数字当成数组长度:

const arr = Array.of(1, 2)
console.log(arr.length) // 2

const arr1 = Array.of(3)
console.log(arr1.length) // 1
console.log(arr1[0]) // 3

const arr2 = Array.of('2')
console.log(arr2.length) // 1
console.log(arr2[0]) // '2'

4. Array.from(ES2015)

Array.from() 可以把可迭代对象或类数组对象转换为新数组。它是浅转换,不会递归克隆元素:

function arga(...args) {
  const arg = Array.from(args)
  console.log(arg)
}

arga('arr1', 26, 'from') // ['arr1', 26, 'from']

映射转换

第二个参数是映射函数,会在生成目标数组时对每个元素进行转换:

function arga(...args) {
  return Array.from(args, value => `${value}1`)
}

const arr = arga('arr', 26, 'pop')
console.log(arr) // ['arr1', '261', 'pop1']

如果映射函数需要使用对象作为 this,可以传入第三个参数:

const helper = {
  diff: 1,
  add(value) {
    return typeof value === 'number' ? value + this.diff : `${value}${this.diff}`
  }
}

function translate() {
  return Array.from(arguments, helper.add, helper)
}

const arr = translate('liu', 26, 'man')
console.log(arr) // ['liu1', 27, 'man1']

二、数组方法

数组原型方法概览

  • join():用分隔符把元素连接为字符串
  • push() / pop():从数组末尾添加/删除元素
  • shift() / unshift():从数组开头删除/添加元素
  • slice():返回一段浅复制,不修改原数组
  • splice():在原数组上删除、插入或替换元素
  • fill():用一个值填充指定范围,修改原数组
  • filter():筛选元素,返回新数组
  • concat():连接数组或值,返回新数组
  • indexOf() / lastIndexOf():按严格相等查找索引
  • every() / some():全部满足/至少一个满足
  • includes():按 SameValueZero 判断是否包含值
  • sort():排序并修改原数组
  • reverse():反转并修改原数组
  • forEach():遍历但不产生新数组,不能用 break 提前结束
  • map():映射并返回新数组
  • reduce() / reduceRight():归并为一个值
  • copyWithin():在数组内部复制,修改原数组
  • find() / findIndex():查找第一个满足条件的值/索引
  • toLocaleString() / toString():转换为字符串
  • flat() / flatMap():扁平化一层或多层数组
  • entries() / keys() / values():取得数组迭代器

现代运行时还提供 at()findLast()findLastIndex(),以及不修改原数组的 toReversed()toSorted()toSpliced()with() 等方法,文末会补充。

各个方法的基本功能详解

1. join()

join() 会把数组元素转换为字符串,并使用指定分隔符连接,默认分隔符是逗号。undefinednull 元素会按空字符串处理,方法不会修改原数组:

const arr = [1, 2, 3]
console.log(arr.join()) // 1,2,3
console.log(arr.join('-')) // 1-2-3
console.log(arr) // [1, 2, 3]

通过 join() 可以实现重复字符串;生产代码也可以直接使用更语义化的 String.prototype.repeat()

function repeatString(str, n) {
  return new Array(n + 1).join(str)
}

console.log(repeatString('abc', 3)) // abcabcabc
console.log('Hi'.repeat(5)) // HiHiHiHiHi

2. push() 和 pop()

push() 从数组末尾添加一个或多个元素,并返回新长度;pop() 删除并返回最后一个元素:

const arr = ['Lily', 'lucy', 'Tom']
const count = arr.push('Jack', 'Sean')
console.log(count) // 5
console.log(arr) // ['Lily', 'lucy', 'Tom', 'Jack', 'Sean']

const item = arr.pop()
console.log(item) // Sean
console.log(arr) // ['Lily', 'lucy', 'Tom', 'Jack']

两者都会修改原数组;在频繁操作超大数组的场景中,还要考虑开头/末尾操作的性能和数据结构选择。

3. shift() 和 unshift()

shift() 删除并返回第一个元素;unshift() 从开头添加元素并返回新长度。这两个方法都会修改原数组,开头操作通常需要移动其他索引:

const arr = ['Lily', 'lucy', 'Tom']
const count = arr.unshift('Jack', 'Sean')
console.log(count) // 5
console.log(arr) // ['Jack', 'Sean', 'Lily', 'lucy', 'Tom']

const item = arr.shift()
console.log(item) // Jack
console.log(arr) // ['Sean', 'Lily', 'lucy', 'Tom']

4. sort()

sort() 默认按字符串的 UTF-16 编码单元升序排序,因此数字数组必须提供比较函数。它会修改原数组。现代 ECMAScript 实现从 ES2019 起要求排序稳定,但比较函数仍应尽量满足可传递性等规则:

const arr1 = ['a', 'd', 'c', 'b']
console.log(arr1.sort()) // ['a', 'b', 'c', 'd']

const arr2 = [13, 24, 51, 3]
console.log(arr2.sort()) // [13, 24, 3, 51]
console.log(arr2) // 原数组已改变

比较函数返回负数表示 a 排在 b 前,返回 0 表示等价,返回正数表示 a 排在 b 后:

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

const arr2 = [13, 24, 51, 3]
console.log(arr2.sort(compare)) // [3, 13, 24, 51]

降序可以交换参数:

const arr2 = [13, 24, 51, 3]
console.log(arr2.sort((a, b) => b - a)) // [51, 24, 13, 3]

5. reverse()

reverse() 颠倒元素顺序并修改原数组:

const arr = [13, 24, 51, 3]
console.log(arr.reverse()) // [3, 51, 24, 13]
console.log(arr) // [3, 51, 24, 13]

6. concat()

concat() 连接一个或多个数组和值,返回新数组,不修改原数组。数组参数默认只展开一层,因此它是浅操作:

const arr = [1, 3, 5, 7]
const arrCopy = arr.concat(9, [11, 13])
console.log(arrCopy) // [1, 3, 5, 7, 9, 11, 13]
console.log(arr) // [1, 3, 5, 7]

const arrCopy2 = arr.concat([9, [11, 13]])
console.log(arrCopy2) // [1, 3, 5, 7, 9, [11, 13]]
console.log(arrCopy2[5]) // [11, 13]

对象也可以通过 Symbol.isConcatSpreadable 控制是否被展开,这是少见的高级用法。

7. slice()

slice(start, end) 返回从 start(包含)到 end(不包含)的浅复制,不修改原数组。负数会从数组末尾倒数,超出范围会被规范化到有效边界:

const arr = [1, 3, 5, 7, 9, 11]
const arrCopy = arr.slice(1)
const arrCopy2 = arr.slice(1, 4)
const arrCopy3 = arr.slice(1, -2) // 等价于 arr.slice(1, 4)
const arrCopy4 = arr.slice(-4, -1) // 等价于 arr.slice(2, 5)

console.log(arr) // [1, 3, 5, 7, 9, 11]
console.log(arrCopy) // [3, 5, 7, 9, 11]
console.log(arrCopy2) // [3, 5, 7]
console.log(arrCopy3) // [3, 5, 7]
console.log(arrCopy4) // [5, 7, 9]

8. splice()

splice(start, deleteCount, ...items) 会修改原数组,并返回被删除元素组成的数组,可以删除、插入或替换。

删除元素

const arr = [1, 3, 5, 7, 9, 11]
const arrRemoved = arr.splice(0, 2)
console.log(arr) // [5, 7, 9, 11]
console.log(arrRemoved) // [1, 3]

向指定索引处添加元素

const array1 = [22, 3, 31, 12]
const removed = array1.splice(1, 0, 12, 35)
console.log(removed) // []
console.log(array1) // [22, 12, 35, 3, 31, 12]

替换指定索引位置的元素

const array1 = [22, 3, 31, 12]
const removed = array1.splice(1, 1, 8)
console.log(removed) // [3]
console.log(array1) // [22, 8, 31, 12]

9. indexOf() 和 lastIndexOf()

两者接收要查找的值和可选的起始索引,使用严格相等比较,因此不会找到 NaN;未找到时返回 -1lastIndexOf 从后向前查找:

const arr = [1, 3, 5, 7, 7, 5, 3, 1]
console.log(arr.indexOf(5)) // 2
console.log(arr.lastIndexOf(5)) // 5
console.log(arr.indexOf(5, 2)) // 2
console.log(arr.lastIndexOf(5, 4)) // 2
console.log(arr.indexOf('5')) // -1
console.log([NaN].indexOf(NaN)) // -1

10. forEach()

forEach() 对每个实际存在的元素调用回调,回调参数依次是 elementindexarray。它返回 undefined,会跳过稀疏数组空位,不能用 break 提前结束;需要产生新数组时使用 map,需要提前结束时可以考虑 for...ofsome 或普通 for

const arr = [11, 22, 33, 44, 55]
arr.forEach((value, index, array) => {
  console.log(`${value}|${index}|${array === arr}`)
})

// 11|0|true
// 22|1|true
// 33|2|true
// 44|3|true
// 55|4|true

forEach 是 ES5 方法,不是“ES5 及以下”的旧循环语法;它在现代浏览器和 Node.js 中都很常用。

11. map()

map() 是 ES5 方法,返回一个新数组,回调参数与 forEach 相同,不修改原数组。它会跳过原数组空位,并在结果中保留对应空位:

const arr = [1, 2, 3, 4, 5]
const arr2 = arr.map(item => item * item)
console.log(arr2) // [1, 4, 9, 16, 25]
console.log(arr) // [1, 2, 3, 4, 5]

12. filter()

filter() 对每个实际存在的元素执行回调,把回调结果为真值的元素放入新数组:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const arr2 = arr.filter((value, index) => index % 3 === 0 || value >= 8)
console.log(arr2) // [1, 4, 7, 8, 9, 10]

13. fill()(ES2015)

fill(value, start, end) 用同一个值填充范围,包含 start、不包含 end,并修改原数组。若填充的是对象,所有位置会共享同一个对象引用:

const arr = [1, 2, 3, 'cc', 5]
arr.fill(1)
console.log(arr) // [1, 1, 1, 1, 1]

const arr2 = [1, 2, 3, 'arr', 5]
arr2.fill(1, 2)
console.log(arr2) // [1, 2, 1, 1, 1]
arr2.fill(0, 1, 3)
console.log(arr2) // [1, 0, 0, 1, 1]

const objects = Array(2).fill({ count: 0 })
objects[0].count++
console.log(objects[1].count) // 1,共享引用

14. every()

every() 只有在所有元素都满足条件时才返回 true;空数组会返回 true,这是“没有反例”的逻辑结果:

const arr = [1, 2, 3, 4, 5]
console.log(arr.every(value => value < 10)) // true
console.log(arr.every(value => value < 3)) // false
console.log([].every(Boolean)) // true

15. some()

some() 只要有一个元素满足条件就返回 true,空数组返回 false

const arr = [1, 2, 3, 4, 5]
console.log(arr.some(value => value < 3)) // true
console.log(arr.some(value => value < 1)) // false
console.log([].some(Boolean)) // false

16. includes()(ES2016)

includes(value, fromIndex) 判断数组是否包含指定值,使用 SameValueZero 比较,因此 NaN 与自身相等,-00 也相等:

const array1 = [22, 3, 31, 12, 'arr']
console.log(array1.includes(31)) // true
console.log(array1.includes(31, 3)) // false,从索引 3 开始查找

const values = [1, NaN, 2]
console.log(values.indexOf(NaN)) // -1
console.log(values.includes(NaN)) // true

17. reduce() 和 reduceRight()

reduce() 从左到右处理,reduceRight() 从右到左处理。回调通常接收累加器、当前值、当前索引和原数组;第二个参数是初始累加值。没有初始值时,首个元素会作为初始累加值,空数组会抛出 TypeError

const values = [1, 2, 3, 4, 5]
const sum = values.reduceRight((prev, cur) => prev + cur, 10)
console.log(sum) // 25

const product = values.reduce((prev, cur) => prev * cur, 1)
console.log(product) // 120

18. toLocaleString() 和 toString()

toString() 通常以逗号连接元素;toLocaleString() 会把每个元素按当前区域设置转换,日期、数字和嵌套数组的结果可能随环境和 locale 改变:

const array1 = [22, 3, 31, 12]
console.log(array1.toLocaleString()) // 22,3,31,12(结果受 locale 影响)
console.log(array1.toString()) // 22,3,31,12

19. find() 和 findIndex()

两者都接收回调和可选的 thisArg,回调参数为元素、索引和数组;找到第一个满足条件的元素后停止。find 返回值,找不到返回 undefinedfindIndex 返回索引,找不到返回 -1

const arr = [1, 2, 3, 'arr', 5, 1, 9]

console.log(arr.find(value => value > 2)) // 3
console.log(arr.findIndex(value => value > 2)) // 2
console.log(arr.find(value => value > 10)) // undefined

20. copyWithin()(ES2015)

copyWithin(target, start, end) 在数组内部复制元素,包含 start、不包含 end,会修改原数组,并且复制过程能正确处理重叠范围:

const arr = [1, 2, 3, 'arr', 5]
arr.copyWithin(3, 0)
console.log(arr) // [1, 2, 3, 1, 2]

const arr2 = [1, 2, 3, 'arr', 5, 9, 17]
arr2.copyWithin(3, 0, 3)
console.log(arr2) // [1, 2, 3, 1, 2, 3, 17]

21. flat() 和 flatMap()(ES2019)

flat(depth) 按指定深度递归展开并返回新数组,默认深度为 1;Infinity 可以展开任意嵌套深度。它还会跳过被展开层级中的空位:

const arr1 = [0, 1, 2, [3, 4]]
console.log(arr1.flat()) // [0, 1, 2, 3, 4]

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

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

const sparse = [1, 2, , 4]
console.log(sparse.flat()) // [1, 2, 4]

flatMap() 相当于先 map(),再以深度 1 执行 flat(),不能一次展开任意深度:

const values = [2, 3, 4]
const result = values.flatMap(value => [value, value * 2])
console.log(result) // [2, 4, 3, 6, 4, 8]

22. entries()、keys() 和 values()(ES2015)

这三个方法都返回数组迭代器:keys() 遍历索引,values() 遍历值,entries() 遍历 [index, value]

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

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

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

也可以手动调用迭代器的 next()

const letters = ['a', 'b', 'c']
const entries = letters.entries()
console.log(entries.next().value) // [0, 'a']
console.log(entries.next().value) // [1, 'b']
console.log(entries.next().value) // [2, 'c']
console.log(entries.next()) // { value: undefined, done: true }

三、现代数组方法补充

1. at()

at(index) 支持负索引,适合读取末尾元素;它不会修改数组:

const values = [10, 20, 30]
console.log(values.at(0)) // 10
console.log(values.at(-1)) // 30
console.log(values.at(99)) // undefined

2. findLast() 和 findLastIndex()

这两个方法从数组末尾开始查找:

const values = [1, 2, 3, 2]
console.log(values.findLast(value => value % 2 === 0)) // 2
console.log(values.findLastIndex(value => value % 2 === 0)) // 3

3. 不修改原数组的复制方法

toReversed()toSorted()toSpliced()with() 是 ES2023 的“复制后修改”方法,适合不可变数据风格:

const values = [3, 1, 2]
const sorted = values.toSorted((a, b) => a - b)
const reversed = values.toReversed()
const replaced = values.with(1, 9)
const spliced = values.toSpliced(1, 1, 8)

console.log(values) // [3, 1, 2]
console.log(sorted) // [1, 2, 3]
console.log(reversed) // [2, 1, 3]
console.log(replaced) // [3, 9, 2]
console.log(spliced) // [3, 8, 2]

如果目标浏览器或 Node.js 版本较旧,应通过兼容性数据或构建目标确认支持情况;不要仅根据“支持 ES6”推断支持所有后续数组方法。

4. Array.fromAsync()

现代运行时还提供 Array.fromAsync(),可以把异步可迭代对象、同步可迭代对象或类数组对象转换为 Promise 数组结果。它适合需要按顺序等待异步元素的场景:

async function demo() {
  const result = await Array.fromAsync([1, 2, 3], async value => value * 2)
  console.log(result) // [2, 4, 6]
}

demo()

5. 选择方法时的建议

  • 只读访问优先考虑 atfindfindLast 等方法。
  • 需要改变原数组时使用 pushsplicesort 等,并在函数命名或注释中明确副作用。
  • 需要不可变更新时优先使用 toSortedtoSplicedwith,或使用展开运算符创建浅副本。
  • 数组只包含一层引用时,sliceconcat、展开、Array.from 都只是浅复制;需要深复制时,应根据数据类型和兼容目标选择 structuredClone 或专门的序列化方案。
  • 不要把稀疏数组空位和显式 undefined 混为一谈;不同方法对空位的处理不同。

参考资料

  1. MDN:Array
  2. MDN:Array.prototype.sort()
  3. MDN:Array.prototype.toSorted()
  4. MDN:Array.prototype.at()
  5. MDN:Array.from()
  6. MDN:Array.of()

作者:Yushia

链接:https://juejin.cn/post/6907109642917117965

来源:稀土掘金。著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS