🔥 一文掌握 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" 是提升代码健壮性的关键升级。
  • 掌握常见配方(去重/分组/索引化/集合运算/不可变更新),你的业务代码会又短又稳。
  • 牢记坑位图复杂度,性能与正确性两手抓。
相关推荐
i听风逝夜9 小时前
Web 3D地球实时统计访问来源
前端·后端
iMonster9 小时前
React 组件的组合模式之道 (Composition Pattern)
前端
呐呐呐呐呢9 小时前
antd渐变色边框按钮
前端
元直数字电路验证9 小时前
Jakarta EE Web 聊天室技术梳理
前端
wadesir10 小时前
Nginx配置文件CPU优化(从零开始提升Web服务器性能)
服务器·前端·nginx
牧码岛10 小时前
Web前端之canvas实现图片融合与清晰度介绍、合并
前端·javascript·css·html·web·canvas·web前端
灵犀坠10 小时前
前端面试八股复习心得
开发语言·前端·javascript
9***Y4810 小时前
前端动画性能优化
前端
网络点点滴10 小时前
Vue3嵌套路由
前端·javascript·vue.js
牧码岛10 小时前
Web前端之Vue+Element打印时输入值没有及时更新dom的问题
前端·javascript·html·web·web前端