DevTracePad↗
简体中文 ⌄
English简体中文日本語

实践手册 / 代码讲解

用输入输出约束读懂代码

有用的解释应说明哪些内容改变了、哪些仍共享引用,以及哪些输入会破坏假设。

先写约束,再讲流程

对于 uniqueById,更精确的约束是“接收带有 ID 的非 null 对象数组,返回每个不同键对应的最后一个对象”,而不是笼统地说“去重”。这让身份判断依据和重复项策略变得明确。如果调用方希望保留第一次出现的对象,那么即便结果没有重复,代码也不符合需求。

跟踪值,而不只是逐行翻译

map 操作构造键值对,Map 构造器读取它们。后出现的相同键替换已存的值,但迭代顺序仍取决于每个键第一次插入的位置。最终展开得到新数组。用三个对象、其中一个重复键来演示,才能清楚区分“值被替换”和“位置不移动”。只用两个对象不容易展示这种组合行为。

检查看不见的引用共享

新数组不等于新对象。修改输出数组里的对象,可能同时影响输入引用的对象。要确定调用方需要浅层集合,还是独立副本。还要检查缺失 ID 和混合类型:两个缺失 ID 会在 undefined 键合并,而 1 和 "1" 保持不同。这些是需要与需求核对的行为,不一定就是缺陷。

把解释变成检查项

准备空输入、单个对象、重复 ID、缺失 ID 和非法项。在运行前先写预期输出。如果助手声称代码会排序或深拷贝,就寻找完成该操作的代码;这个片段中并不存在。好的解释应能与可见代码逐项对应。

拆解一个具体例子

代码讲解

编写示例:这份固定结果仅对应示例原文,不是实时 AI 回复。

原始内容

function uniqueById(items) {
  return [...new Map(items.map(item => [item.id, item])).values()];
}

解析结果

用途
每个不同的 id 保留一个对象。

处理过程
items.map 生成 [id, 对象] 键值对。Map 为每个键保留一个值,后续重复键覆盖其值,但不改变键最初的位置。展开操作创建新数组。

关键细节
输入 [{id:1,v:"a"},{id:2,v:"b"},{id:1,v:"c"}] 时,预期结果为 [{id:1,v:"c"},{id:2,v:"b"}]。这是推导示例,不是执行日志。

边界
对象仍与输入共享引用,并非深拷贝。缺失 id 的项合并到 undefined 键。null 项会抛错。数字 1 和字符串 "1" 是不同键。