数组的所有方法:怎么分类记?哪些会改原数组?哪些最容易被追问?
面试速答(30 秒版 TL;DR)
- 这篇只讨论
Array构造器 和Array.prototype的标准方法,不展开TypedArray。 - 记数组方法最稳的方式不是死背,而是按 5 类记:创建、增删改、查找判断、遍历变换、迭代器/拷贝新数组。
- 面试最高频的是:哪些会修改原数组、
map/forEach/filter/reduce区别、slicevssplice、sort默认行为、includesvsindexOf、flat/flatMap、find/findLast。 - 新版数组 API 要会:
at、findLast、findLastIndex、toReversed、toSorted、toSpliced、with、Array.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) slice 和 splice 别混
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) | 替换指定位置,返回新数组 |
新版“不可变数组方法”为什么重要
面试常见口径:
reverse、sort、splice会改原数组,容易污染状态- React / Redux / 函数式编程里更偏好返回新数组
- 所以 ES 新增了
toReversed、toSorted、toSpliced、with
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?) | boolean | 用 SameValueZero 比较,能识别 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 能匹配 NaN,indexOf 不能。
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...ofsomeeveryfind
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:哪些数组方法会修改原数组?
标准答法:
- 会改原数组的常见方法有:
push、pop、shift、unshift、splice、sort、reverse、fill、copyWithin - 其余大多数高阶方法如
map、filter、slice、concat、flat都返回新数组 - ES 新增的
toSorted、toReversed、toSpliced、with是专门给“不可变更新”准备的
Q2:map、forEach、filter、reduce 怎么选?
- 要副作用,不关心返回值:
forEach - 一对一转换:
map - 筛选子集:
filter - 聚合成一个值:
reduce
Q3:为什么说数组去重常用 Set?
const unique = [...new Set(arr)];
因为表达直接、代码短、时间复杂度通常更合理。
易错点/坑
sort()默认按字符串排序,不是数值排序。fill({})会重复使用同一个对象引用。forEach不能break/continue。map里忘记return,得到一堆undefined。includes能找NaN,indexOf不行。slice不改原数组,splice会改。
速记要点(可背诵)
- 先背“会不会改原数组”,再背“按功能分类”。
- 查找判断:
includes、find、some、every。 - 遍历变换:
map、filter、reduce、flat。 - 不可变更新新 API:
toSorted、toReversed、toSpliced、with。