A06 数组与对象
讲解 JS 数组与对象的常用操作,重点说明 sort 原地修改陷阱与不可变写法,提供方法对照表、分组排序安全写法及常见问题排查。
- 数组方法分原地修改与返回新数组两类,sort/reverse/splice 会污染原状态,应优先用 toSorted/toReversed
- sort 默认按字符串比较,数字排序必须传比较函数 (a,b)=>a.key-b.key
- 更新列表项用 map + 展开替换,删除对象字段用解构 rest,保持不可变
- 分组优先 Object.groupBy,旧环境用 reduce 手写;展开复制仅一层,嵌套需逐层处理或 structuredClone
A06 数组与对象
摘要:数组存有序列表,对象存具名记录,两者构成页面数据的全部载体。数组的
map/filter/sort与对象的展开、解构、Object.entries决定代码能否保持不可变。sort原地修改且默认按字符串比较,是最容易出错的两个细节。本文给出常用方法对照表、按字段分组与排序的安全写法。
关键词:数组,对象,解构,不可变,排序
所属:frontend/js / A-入门 | 上一篇:A05-函数与参数 | 下一篇:A07-字符串与数字处理
1 引言
列表页的典型需求是:过滤、排序、按字段分组、取一条详情、更新其中一项。用 for 循环逐条改写能实现,但原数组会被污染,UI 与状态容易不同步。
本文给出四类操作的标准写法:读取、转换、更新、聚合。范围见表 1-1。
| 项 | 本文覆盖 | 本文不覆盖 |
|---|---|---|
| 数组 | 增删改查、map/filter/sort/reduce |
类型化数组 |
| 对象 | 展开、解构、entries、可选链 |
属性描述符(C02) |
| 不可变 | 展开复制、toSorted/toReversed |
深拷贝(C06) |
2 预备知识
| 项 | 要求 | 核对方式 |
|---|---|---|
| 环境 | Node.js 20+(含 toSorted) |
node -v 确认版本 |
| 前置 | A05 | 能写纯函数 |
| 文件 | js-practice/collections.js |
编码 UTF-8 |
3 原理与实现
3.1 可变与不可变两组方法
数组方法分两类:一类原地修改,一类返回新数组,如图 3-1 所示。
flowchart LR
A["原数组 arr"] --> B["原地修改<br/>push·splice·sort·reverse"]
A --> C["返回新数组<br/>map·filter·slice·toSorted"]
B --> D["arr 被改变<br/>旧引用同步变化"]
C --> E["arr 不变<br/>旧引用保持原值"]
图 3-1 原地修改与返回新数组的方法分属两条路径
图上可见:
sort同时出现在原地修改一列,复制后再排序才能避免污染状态;toSorted是返回新数组的替代品。
3.2 完整实现
// js-practice/collections.js;运行环境:Node.js 20+
const products = [
{ id: 3, name: '键盘', price: 299, category: 'input' },
{ id: 1, name: '显示器', price: 1299, category: 'output' },
{ id: 2, name: '鼠标', price: 99, category: 'input' },
];
// 1 排序:复制后排序,比较函数必须返回数字
const byPriceAsc = products.toSorted((a, b) => a.price - b.price);
console.log(byPriceAsc.map((p) => p.name)); // ['鼠标','键盘','显示器']
console.log(products[0].name); // 键盘,原数组未变
// 2 原地 sort 的默认行为是字符串比较
console.log([10, 9, 100].sort()); // [10, 100, 9]
console.log([10, 9, 100].sort((a, b) => a - b)); // [10, 9, 100]
// 3 过滤与映射链式组合
const cheapInputs = products
.filter((p) => p.category === 'input' && p.price < 200)
.map((p) => ({ ...p, label: `${p.name} ¥${p.price}` }));
console.log(cheapInputs); // [ { id: 2, name: '鼠标', price: 99, category: 'input', label: '鼠标 ¥99' } ]
// 4 按字段分组
const byCategory = Object.groupBy(products, (p) => p.category);
console.log(Object.keys(byCategory)); // ['input','output']
// 5 更新一项而不改原数组
const updated = products.map((p) => (p.id === 2 ? { ...p, price: 89 } : p));
console.log(updated[2].price, products[2].price); // 89 99
// 6 解构取值与可选链
const [{ name: firstName }, ...rest] = products;
const detail = products.find((p) => p.id === 9);
console.log(firstName, rest.length, detail?.name ?? '不存在'); // 键盘 2 不存在
// 7 对象遍历
for (const [key, value] of Object.entries({ a: 1, b: 2 })) {
console.log(key, value);
}
常用方法对照见表 3-1。
| 需求 | 方法 | 是否改原数组 | 返回 |
|---|---|---|---|
| 追加 / 删除末尾 | push / pop |
是 | 长度 / 元素 |
| 删除指定位置 | splice |
是 | 被删元素数组 |
| 截取片段 | slice |
否 | 新数组 |
| 排序 | sort / toSorted |
是 / 否 | 原数组 / 新数组 |
| 反转 | reverse / toReversed |
是 / 否 | 原数组 / 新数组 |
| 查找索引 | findIndex / indexOf |
否 | 索引或 -1 |
| 判断包含 | includes / some |
否 | 布尔值 |
| 合并 | concat / 展开 |
否 | 新数组 |
对象操作的等价写法见表 3-2。
| 目的 | 写法 | 说明 |
|---|---|---|
| 取安全属性 | obj?.a?.b |
空值短路,不抛错 |
| 给默认值 | obj.a ?? 0 |
0 与 "" 保留 |
| 复制并覆盖 | { ...obj, a: 1 } |
后者覆盖前者 |
| 删除字段 | const { a, ...next } = obj |
生成不含 a 的新对象 |
| 键值列表 | Object.entries(obj) |
用于转表格、转 Map |
4 验证与讨论
- 执行脚本,确认
byPriceAsc顺序为['鼠标','键盘','显示器'],且原数组首项仍为键盘。 - 把
toSorted改为sort,再打印原数组,确认被改变。 - 注释
sort的比较函数,观察[10, 9, 100]变为[10, 100, 9]。 - 用
Object.groupBy的分组结果渲染表格,确认分组键与数据一一对应。
| 检查项 | 预期结果 | 说明 |
|---|---|---|
| 不可变排序 | 原数组顺序不变 | toSorted 返回新数组 |
| 数字排序 | 升序正确 | 必须传比较函数 |
| 链式过滤 | 只保留符合两项条件的记录 | filter 支持复合条件 |
| 局部更新 | 仅目标项变化 | map + 展开替换 |
讨论与局限。 Object.groupBy 需要较新运行时,旧环境需手写 reduce 分组。另,展开复制只处理一层,嵌套对象仍共享引用;sort 在元素数量大时不稳定以外的比较成本可忽略,但比较函数内做类型转换会显著变慢。
5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 排序结果像随机 | 未传比较函数,按字符串比较 | 传 (a, b) => a.key - b.key |
| 原数组被意外修改 | 用了 sort/reverse/splice |
改用 toSorted 或先 slice() |
| 对象展开后仍同步变化 | 嵌套对象未复制 | 逐层展开或用 structuredClone |
| 遍历对象顺序不符合预期 | 依赖了属性顺序 | 用 Object.entries 后显式排序 |
find 返回 undefined 后报错 |
未判空 | 用 ?. 或先判 if (!item) |
6 结论
| 序号 | 结论 | 依据 |
|---|---|---|
| 1 | sort 原地修改且默认按字符串比较 |
3.2 节 |
| 2 | 保持不可变优先用 toSorted/toReversed |
图 3-1 |
| 3 | 更新列表项用 map + 展开 |
3.2 节 |
| 4 | 分组优先 Object.groupBy,旧环境用 reduce |
表 3-1 |
- 上一篇:A05-函数与参数
- 下一篇:A07-字符串与数字处理
- 返回:A00-总结 | 科目 README
参考文献
[1] MDN Web Docs · Array,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array(访问于 2026-10-31)
[2] MDN Web Docs · Object.groupBy,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy(访问于 2026-10-31)

评论 0
还没有评论,快来抢沙发吧~