🔥 一文掌握 JavaScript 数组方法(2025 全面指南):分类解析 × 业务场景 × 易错点

🔥 一文掌握 JavaScript 数组方法(2025 全面指南):分类解析 × 业务场景 × 易错点

本文目标:让你在业务里正确选用数组方法 ,写出可维护、无副作用、性能靠谱 的代码。

适用对象:从前端入门到高级进阶,尤其是写 React/Vue/Node 的同学。


1)概览:如何选用数组方法

第一原则:能用纯函数就不用会改原数组的方法。

在状态管理(React/Vue/Redux/MobX)与多人协作中,副作用常是 Bug 根源。

  • 纯函数(不改原数组)map / filter / slice / concat / flat / reduce / find / some / every / includes / indexOf / findLast* / at / ES2023toSorted / toReversed / toSpliced / with
  • ⚠️ 会修改原数组push / pop / shift / unshift / splice / sort / reverse / fill / copyWithin

想到这里:有"toXxx" 就是"返回新数组的版本" (ES2023"Change-Array-by-Copy"提案带来的)。


2)会修改原数组(非纯函数,慎用⚠️)

2.1 栈/队列:push / pop / unshift / shift

  • 是否修改:✅ 修改

  • 复杂度push/pop摊还 O(1);unshift/shift → O(n)(头部移动会搬迁元素

  • 业务场景

    • 聊天消息流:新消息 push 到尾部;撤回最后一条 pop
    • 滑动窗口:push 添加,超长后 shift 丢弃头部(注意性能
  • 易错点

    • 高频 unshift/shift 可能退化性能(每次操作搬迁 n 个元素)
  • 最佳实践

    • 需要频繁头部操作 → 用双端队列 (如 deque 数据结构)或 链表,或转成环形缓冲
scss 复制代码
// ✅ React 状态中避免直接 push
setList(prev => [...prev, newItem]); // 纯函数写法

2.2 通用改动:splice(start, deleteCount, ...items)

  • 是否修改:✅ 修改

  • 返回值:被删除的元素数组

  • 复杂度:O(n)(可能移动大量元素)

  • 场景:购物车删除/替换商品;表格行插入/删除

  • 易错点

    • delete arr[i] 会留下空洞 (sparse),不要用它删除,改用 splice
  • 纯函数替代toSpliced(ES2023)或 slice 拼接

scss 复制代码
// 删除索引 idx 处元素(纯函数)
const removeAt = (arr, idx) => arr.slice(0, idx).concat(arr.slice(idx + 1));

2.3 排序/反转:sort(compareFn) / reverse()

  • 是否修改:✅ 修改

  • 复杂度:O(n log n)

  • 场景:商品按价/热度排序;时间线反转

  • 易错点

    • sort() 不传比较函数时按字典序[2,10,3].sort() // [10,2,3]
    • sortES2019 起规范要求稳定排序(大多数现代引擎实现稳定)
  • 国际化

    • 中文/多语言排序用 Intl.CollatorlocaleCompare
  • 纯函数替代toSorted / toReversed

css 复制代码
prices.toSorted((a, b) => a - b);       // 数字升序(不改原数组)
names.toSorted((a, b) => a.localeCompare(b, 'zh-Hans-u-co-pinyin'));

2.4 就地填充/复制:fill / copyWithin

  • 是否修改:✅ 修改

  • 场景:初始化缓存区;滑动窗口构造

  • 易错点

    • Array(3).fill({})同一个对象引用三次
    • 需要不同对象:Array.from({length:3}, () => ({}))

3)不修改原数组(纯函数,推荐✅)

3.1 遍历与映射:forEach / map

  • 是否修改:❌ 不改(但你在回调里若改外部对象,依然有副作用)

  • 返回值forEachundefinedmap → 新数组

  • 场景:接口数据转展示结构;字段格式化

  • 易错点

    • forEach 不能使用 break/return 提前结束 ;可用 some/every 或普通 for 循环
  • 最佳实践

    • 链式调用filter().map().reduce();注意内存(会生成中间数组)
php 复制代码
// 列表转 UI 选项
const options = users.map(u => ({ label: u.name, value: u.id }));

3.2 查找类:find / findIndex / findLast / findLastIndex / includes / indexOf / lastIndexOf / at

- **是否修改**:❌ 不改

  • 返回值:元素/索引/布尔

  • 场景

    • RBAC 权限:roles.includes('admin')
    • 搜索最近匹配项:日志/时间线用 findLast*
    • 负索引访问:arr.at(-1) 取最后一个
  • 易错点

    • includesindexOfNaN 行为不同
      includes(NaN)trueindexOf(NaN)-1
    • 没有 any 这个方法,判断"存在任意满足项"请用 some
  • 对比

    • find 找值;findIndex 找索引;找不到分别为 undefined / -1
    • includes 适合纯等值 (SameValueZero);find 适合条件判断

3.3 过滤 & 判定:filter / some / every

  • 是否修改:❌ 不改

  • 复杂度:O(n)

  • 场景

    • 列表筛选:库存 > 0、状态为启用
    • 表单校验:every(field => !!field.value)
    • 早停逻辑:some/every短路(性能友好)

3.4 拼接 & 裁剪:concat / slice

  • 是否修改:❌ 不改

  • 场景

    • 无限滚动分页:list = list.concat(nextPage)
    • 复制数组:const copy = arr.slice()(或 [...arr]
  • 易错点

    • sliceend 不含本身(半开区间)

3.5 扁平化与映射:flat / flatMap

  • 是否修改:❌ 不改

  • 场景

    • 树形菜单拍平渲染;评论楼层降维
  • 细节

    • flat(depth=1)flatMap(fn) 等价 arr.map(fn).flat(1)
    • flat移除空洞(sparse entry)

3.6 归约:reduce

  • 是否修改:❌ 不改

  • 场景

    • 汇总统计、分组、索引化(id -> item Map)
  • 易错点

    • 务必传初始值 ,否则空数组会抛 TypeError
  • 配方

    • 求和:arr.reduce((s,x)=>s+x, 0)
    • 分组:见第 7 节"业务实战"

4)ES6+ / ES2023+ 新方法(🆕 非破坏性更安全)

以下方法极大改善了"不要改原数组"的开发体验,尤其适合 React/Vue 状态更新。

  • ES2022

    • at(index):支持负索引读:arr.at(-1)
  • ES2023(变更复制家族,推荐)

    • toSorted(compareFn):像 sort,但返回新数组
    • toReversed():像 reverse,但返回新数组
    • toSpliced(start, deleteCount, ...items):像 splice,但返回新数组
    • with(index, value):返回替换后新数组(不会改原数组)
  • ES2023(查找)

    • findLast / findLastIndex:从尾到头搜索最近匹配项

迁移建议:统一替换 项目里对 sort/reverse/splice 的直接调用,避免无意改动原数组导致的状态异常。


5)迭代器与遍历语法选择

  • for...of:按值遍历(尊重迭代器),推荐
  • for...in:遍历可枚举属性键(含原型链),不推荐用于数组
  • entries() / keys() / values():获得显式迭代器
  • 生成器/惰性序列:在超大数据流里避免中间数组(进阶)
css 复制代码
for (const [i, v] of arr.entries()) {
  // i: 索引,v: 值
}

6)静态方法 & 工具化技巧

注意辨别:下面第一行才是"静态方法(挂在 Array 上)" ;很多易混的其实是"原型方法"。

  • 静态方法(Array.*)

    • Array.isArray(x):跨 iframe 也准确
    • Array.from(iterable, mapFn?, thisArg?):把可迭代/类数组转真数组;遇空洞会转为 undefined (例如 [...[,1,,2]]
    • Array.of(...items):参数即元素(避免 Array(3) 语义歧义)
  • 原型方法但常被误认是静态的(且会改动!)

    • fillcopyWithin会修改原数组
    • includes原型方法 (不是 Array.includes
  • 工具化小技

    • 稳妥初始化不同对象:

      ini 复制代码
      const list = Array.from({ length: 3 }, () => ({}));

7)业务实战配方(复制即用)

7.1 去重

dart 复制代码
// 基础类型
const unique = arr => [...new Set(arr)];

// 对象按字段去重
const uniqueBy = (arr, key) => {
  const seen = new Set();
  return arr.filter(item => (seen.has(item[key]) ? false : (seen.add(item[key]), true)));
};

7.2 分组(groupBy)

ini 复制代码
const groupBy = (arr, keyFn) => arr.reduce((acc, cur) => {
  const k = keyFn(cur);
  (acc[k] ||= []).push(cur);
  return acc;
}, Object.create(null));

// 用法:按城市分组用户
const groups = groupBy(users, u => u.city);

7.3 建索引(id → item)

javascript 复制代码
const indexBy = (arr, keyFn) =>
  arr.reduce((m, x) => (m.set(keyFn(x), x), m), new Map());

7.4 幂等更新(不可变写法)

ini 复制代码
// 更新 id=3 的条目
const updateOne = (list, id, patch) =>
  list.map(item => item.id === id ? { ...item, ...patch } : item);

7.5 插入/删除(不可变)

ini 复制代码
const insertAt = (arr, idx, x) => arr.slice(0, idx).concat(x, arr.slice(idx));
const removeAt = (arr, idx)    => arr.slice(0, idx).concat(arr.slice(idx + 1));

7.6 排序(可读 & 国际化)

scss 复制代码
const by = (...fns) => (a, b) => {
  for (const f of fns) {
    const d = f(a, b);
    if (d) return d;
  }
  return 0;
};
const asc  = sel => (a, b) => (sel(a) > sel(b)) - (sel(a) < sel(b));
const desc = sel => (a, b) => (sel(b) > sel(a)) - (sel(b) < sel(a));

products.toSorted(by(asc(p => p.price), desc(p => p.rating)));

7.7 集合运算(并/交/差)

javascript 复制代码
const union       = (a, b) => [...new Set([...a, ...b])];
const intersection= (a, b) => { const s=new Set(b); return a.filter(x => s.has(x)); };
const difference  = (a, b) => { const s=new Set(b); return a.filter(x => !s.has(x)); };

7.8 分页拼接与去重

ini 复制代码
const mergePage = (prev, next, key) => {
  const map = new Map();
  [...prev, ...next].forEach(x => map.set(x[key], x));
  return [...map.values()];
};

7.9 安全求和/平均/中位数

ini 复制代码
const sum   = (arr, sel = x => x) => arr.reduce((s,x)=>s+sel(x), 0);
const avg   = (arr, sel = x => x) => arr.length ? sum(arr, sel)/arr.length : 0;
const median= (arr, sel = x => x) => {
  const a = arr.map(sel).toSorted((x,y)=>x-y);
  const n = a.length, mid = n>>1;
  return n ? (n%2 ? a[mid] : (a[mid-1]+a[mid])/2) : 0;
};

8)性能与复杂度(Big-O)常识(简版)

类别 方法 时间复杂度 备注
访问 at, 索引读取 O(1) 负索引仅语法层便捷
增删 push/pop 摊还 O(1) 末端操作高效
增删 shift/unshift/splice O(n) 头部/中间会搬迁
遍历 forEach/map/filter/reduce/some/every O(n) some/every 可早停
查找 find/indexOf/includes/findLast* O(n) includes 支持 NaN
排序 sort/toSorted O(n log n) ES2019 起应稳定
扁平 flat/flatMap O(n) flat 移除空洞

链式调用 可读性强但会创建中间数组;超大数据可考虑生成器/惰性管道 或把逻辑合并到单次 reduce


9)易错点与坑位图(强烈建议收藏)

  1. sort 默认字典序 → 数字排序一定传比较函数

  2. includes(NaN) 为真,indexOf(NaN) 为 -1

  3. 没有 any 方法 → 用 some 表达"存在任意满足项"

  4. 删除元素不要用 delete → 留空洞;请用 splice 或纯函数 toSpliced/slice 拼接

  5. forEach 不能 break → 换 some/everyfor...of

  6. Array(3).fill({}) 同引用 → 想要不同对象用 Array.from({length:3}, ()=> ({}))

  7. React/Vue 状态不可变 → 禁止直接 push/splice/sort;用 toSorted/toSpliced 或扩展运算符

  8. 稀疏数组与空洞

    • map/forEach 跳过空洞;flat 会移除空洞;Array.from([...])/... 展开会把空洞转成 undefined
  9. 国际化排序Intl.CollatorlocaleCompare,不要自己写全量拼音表

  10. reduce 一定写初始值,否则空数组崩


10)数组方法大速查表(可打印/收藏)

注:✅ 修改 = 会改原数组;❌ 修改 = 不改原数组;返回值仅列核心。

维度 方法 修改原数组 返回值 典型用途
栈/队 push/pop 新长度 / 弹出项 消息流末端增删
头部 unshift/shift 新长度 / 弹出项 队列头操作(慎用)
改动 splice 删除项数组 中间插入/删除/替换
排序 sort/reverse 排序后原数组 排序/反转(慎用)
填充 fill 原数组 初始化、重置
复制 copyWithin 原数组 局部覆盖复制
遍历 forEach undefined 副作用遍历(统计/日志)
转换 map 新数组 字段映射、UI 适配
过滤 filter 子集 列表筛选
判定 some/every boolean 存在任意/全部满足
查找 find/findIndex 元素/索引 从左找首个匹配
查找 findLast/...Index 元素/索引 从右找最近匹配
包含 includes boolean 是否含某值(含 NaN)
位置 indexOf/lastIndexOf 索引/-1 值等值匹配(不含 NaN)
获取 at 值/undefined 负索引读取
拼接 concat 新数组 合并分页
裁剪 slice 片段 复制/局部视图
扁平 flat/flatMap 新数组 降维/展开列表
归约 reduce 任意 统计/分组/索引化
变更复制 toSorted/toReversed/toSpliced/with 新数组 ES2023 不破坏性替代
静态 Array.isArray --- boolean 类型判定
静态 Array.from --- 新数组 类数组/迭代转真数组
静态 Array.of --- 新数组 以实参数组化

附录:面试/实战高频 Q&A

Q:为什么 React 里 sort() 会导致 UI "莫名其妙"变动?

A:sort 是就地排序,改了原数组引用 。如果这个数组被多个组件共享或被当作依赖,可能触发不可预期的重渲染/比较失败。用 toSortedarr.slice().sort()

Q:如何从数组中删除某个元素但不改原数组?

A:toSpliced(i, 1);或 arr.slice(0,i).concat(arr.slice(i+1))

Q:如何稳定排序多条件?

A:先派生出用于比较的字段(或使用 by/asc/desc 组合器),toSorted 返回新数组,ES2019+ 稳定排序叠加生效。

Q:如何高性能查交集/差集?

A:把较大的那组放到 Set,再 filter 较小那组,时间从 O(n*m) 降到 O(n+m)。


小结

  • 选方法先看是否修改原数组 :业务状态推荐纯函数
  • ES2023 的 "toXxx/with" 是提升代码健壮性的关键升级。
  • 掌握常见配方(去重/分组/索引化/集合运算/不可变更新),你的业务代码会又短又稳。
  • 牢记坑位图复杂度,性能与正确性两手抓。
相关推荐
烛阴28 分钟前
TypeScript 中的 `&` 运算符:从入门、踩坑到最佳实践
前端·javascript·typescript
Java 码农1 小时前
nodejs koa留言板案例开发
前端·javascript·npm·node.js
ZhuAiQuan2 小时前
[electron]开发环境驱动识别失败
前端·javascript·electron
nyf_unknown2 小时前
(vue)将dify和ragflow页面嵌入到vue3项目
前端·javascript·vue.js
胡gh2 小时前
数组开会:splice说它要动刀,map说它只想看看。
javascript·后端·面试
胡gh2 小时前
浏览器:我要用缓存!服务器:你缓存过期了!怎么把数据挽留住,这是个问题。
前端·面试·node.js
你挚爱的强哥2 小时前
SCSS上传图片占位区域样式
前端·css·scss
奶球不是球2 小时前
css新特性
前端·css
Nicholas682 小时前
flutter滚动视图之Viewport、RenderViewport源码解析(六)
前端
无羡仙3 小时前
React 状态更新:如何避免为嵌套数据写一长串 ...?
前端·react.js