跳到主要内容

数组的所有方法:怎么分类记?哪些会改原数组?哪些最容易被追问?

面试速答(30 秒版 TL;DR)

  • 这篇只讨论 Array 构造器Array.prototype 的标准方法,不展开 TypedArray
  • 记数组方法最稳的方式不是死背,而是按 5 类记:创建、增删改、查找判断、遍历变换、迭代器/拷贝新数组
  • 面试最高频的是:哪些会修改原数组map/forEach/filter/reduce 区别、slice vs splicesort 默认行为、includes vs indexOfflat/flatMapfind/findLast
  • 新版数组 API 要会:atfindLastfindLastIndextoReversedtoSortedtoSplicedwithArray.fromAsync

版本说明

  • 现代浏览器 + ES2024 常见 Array 标准库 为准。
  • Array 上的方法分两类:
    • 静态方法:挂在 Array 构造器上,如 Array.isArray
    • 实例方法:挂在 Array.prototype 上,如 map

一、Array 静态方法

方法作用典型场景
Array.isArray(value)判断值是否为数组替代 instanceof Array,跨 realm 更稳
Array.from(arrayLike, mapFn?, thisArg?)把类数组/可迭代对象转成数组NodeList 转数组、顺手映射
Array.fromAsync(items, mapFn?, thisArg?)异步地把 async iterable / iterable 转数组消费异步流、批量收集异步结果
Array.of(...items)按参数原样创建数组避免 new Array(3) 的歧义
Array.isArray([1, 2]); // true
Array.from("abc"); // ["a", "b", "c"]
Array.of(3); // [3]
new Array(3); // 长度为 3 的空槽数组

二、会修改原数组的方法

面试里最容易先问这一组。

方法返回值核心作用
push(...items)新长度尾部追加
pop()被删元素删除最后一项
unshift(...items)新长度头部插入
shift()被删元素删除第一项
splice(start, deleteCount, ...items)被删除元素数组任意位置删/插/替换
sort(compareFn?)原数组引用原地排序
reverse()原数组引用原地反转
fill(value, start?, end?)原数组引用原地填充
copyWithin(target, start, end?)原数组引用原地复制片段到另一个位置

1) slicesplice 别混

const a = [1, 2, 3, 4];

a.slice(1, 3); // [2, 3],不改原数组
a.splice(1, 2, 9); // [2, 3],a 变成 [1, 9, 4]

记忆法:

  • slice:切一段副本出来
  • splice:直接在原数组上“动刀”

2) sort 默认不是“数值升序”

[10, 2, 30].sort(); // [10, 2, 30]
[10, 2, 30].sort((a, b) => a - b); // [2, 10, 30]

原因:默认按 字符串 Unicode 顺序 比较。

3) fill 的对象引用坑

const arr = new Array(3).fill({});
arr[0].x = 1;
console.log(arr); // 三项都带 x,因为填进去的是同一个对象引用

三、不改原数组,但返回新数组的方法

方法作用
concat(...items)拼接数组/值,返回新数组
slice(start?, end?)截取浅拷贝
filter(fn)过滤
map(fn)一对一映射
flat(depth = 1)按深度拍平
flatMap(fn)map + flat(1)
toReversed()返回反转后的新数组
toSorted(compareFn?)返回排序后的新数组
toSpliced(start, deleteCount, ...items)返回“像 splice 处理后的”新数组
with(index, value)替换指定位置,返回新数组

新版“不可变数组方法”为什么重要

面试常见口径:

  • reversesortsplice 会改原数组,容易污染状态
  • React / Redux / 函数式编程里更偏好返回新数组
  • 所以 ES 新增了 toReversedtoSortedtoSplicedwith
const a = [3, 1, 2];

const b = a.toSorted((x, y) => x - y);
console.log(a); // [3, 1, 2]
console.log(b); // [1, 2, 3]

四、查找、判断、取值类方法

方法返回值说明
at(index)元素 / undefined支持负索引
includes(value, fromIndex?)booleanSameValueZero 比较,能识别 NaN
indexOf(value, fromIndex?)下标 / -1找不到返回 -1
lastIndexOf(value, fromIndex?)下标 / -1从右往左找
find(fn)第一个匹配元素找元素本身
findIndex(fn)第一个匹配下标找索引
findLast(fn)最后一个匹配元素从右向左扫描
findLastIndex(fn)最后一个匹配下标从右向左扫描
some(fn)boolean只要有一个满足即可
every(fn)boolean必须全部满足

includes vs indexOf

[NaN].includes(NaN); // true
[NaN].indexOf(NaN); // -1

原因:includes 能匹配 NaNindexOf 不能。

at(-1) 为什么比 arr[arr.length - 1] 更好读

const arr = ["a", "b", "c"];
arr.at(-1); // "c"

五、遍历与聚合方法

方法作用是否适合中断
forEach(fn)遍历执行副作用不适合,不能 break
map(fn)映射成新数组不适合中断
filter(fn)过滤成新数组不适合中断
reduce(fn, initialValue?)左折叠聚合取决于实现
reduceRight(fn, initialValue?)右折叠聚合取决于实现
some(fn)存在即停适合
every(fn)不满足即停适合
find(fn)找到即停适合
findLast(fn)找到即停适合

forEach 为什么不能 break

  • forEach 不是语法级循环,而是高阶函数回调
  • return 只会返回当前回调,不会终止整体遍历
  • 需要中断时,优先:
    • for...of
    • some
    • every
    • find

reduce 的两个高频坑

[1, 2, 3].reduce((sum, x) => sum + x, 0); // 6
  • 最好总是传初始值,避免空数组报错
  • reduce 适合表达“累计结果”,不适合滥用成所有逻辑都往里塞

六、字符串化与迭代器方法

方法作用
join(separator?)按分隔符拼成字符串
toString()转字符串,本质类似 join(",")
toLocaleString(locales?, options?)本地化字符串
keys()遍历索引的迭代器
values()遍历值的迭代器
entries()遍历 [index, value] 的迭代器
[1, 2, 3].join("-"); // "1-2-3"

for (const [i, v] of ["a", "b"].entries()) {
console.log(i, v);
}

七、空槽(hole)是数组题的经典陷阱

const a = [1, , 3];

a.map((x) => x * 2); // [2, empty, 6]
a.forEach((x) => console.log(x)); // 只打印 1 和 3
a.flat(); // [1, 3]

结论:

  • 稀疏数组不是 undefined,而是“这个位置根本没有属性”
  • 很多遍历方法会跳过 hole
  • flat 会把 hole 去掉

典型题 & 标准答法

Q1:哪些数组方法会修改原数组?

标准答法:

  • 会改原数组的常见方法有:pushpopshiftunshiftsplicesortreversefillcopyWithin
  • 其余大多数高阶方法如 mapfiltersliceconcatflat 都返回新数组
  • ES 新增的 toSortedtoReversedtoSplicedwith 是专门给“不可变更新”准备的

Q2:mapforEachfilterreduce 怎么选?

  • 要副作用,不关心返回值:forEach
  • 一对一转换:map
  • 筛选子集:filter
  • 聚合成一个值:reduce

Q3:为什么说数组去重常用 Set

const unique = [...new Set(arr)];

因为表达直接、代码短、时间复杂度通常更合理。


易错点/坑

  • sort() 默认按字符串排序,不是数值排序。
  • fill({}) 会重复使用同一个对象引用。
  • forEach 不能 break / continue
  • map 里忘记 return,得到一堆 undefined
  • includes 能找 NaNindexOf 不行。
  • slice 不改原数组,splice 会改。

速记要点(可背诵)

  • 先背“会不会改原数组”,再背“按功能分类”。
  • 查找判断:includesfindsomeevery
  • 遍历变换:mapfilterreduceflat
  • 不可变更新新 API:toSortedtoReversedtoSplicedwith