先记住这个答案
Map.set(key, value) 和 Set.add(value) 返回当前集合对象,所以可以接着调用 set 或 add;它们执行的是原地修改,并没有每次创建新集合。delete 返回是否实际移除成员的布尔值,clear 返回 undefined,因此不能把同一条调用链无条件接下去。设计 API 时要区分“返回自己方便继续构建”和“返回本次操作结果”,两者服务不同需求。链式表达式也不是事务,中途抛错不会自动撤销已经完成的集合修改;共享状态和错误处理仍需要明确边界。
- set 与 add 返回同一个集合实例
- delete 和 clear 返回的是其他类型
- 连续调用不提供不可变更新或自动回滚
通过身份与返回类型验证链条
下面分别保存 set 和 add 的返回值,与原集合做身份比较。delete 的两次调用显示首次删除为真、再次删除为假,证明返回的是操作结果,而不是一个“删除后的集合”。
clear 的返回值也被直接检查。若代码误写 map.clear().set,后续会访问 undefined;类型系统通常能提示,但 JavaScript 仍需要理解各 API 的真实契约。
const map = new Map();
console.log(map.set('a', 1).set('b', 2) === map);
const set = new Set();
console.log(set.add('a').add('b') === set);
console.log(map.delete('a'), map.delete('a'));
console.log(map.clear() === undefined, map.size);
console.log(set.size);查看输出与解释
true
true
true false
true 0
2set 和 add 可继续调用,因为返回原实例。delete 保留是否实际删除的信息,clear 不返回集合;不同返回设计不能凭方法都属于“修改操作”就当作一致。
共享引用会看到链式执行的每一步
如果 map 同时被缓存管理器和页面模型持有,链式 set 修改的仍是共同对象。框架是否触发渲染取决于自身状态管理契约,返回原实例不能自动满足要求新引用的更新方式。
需要构建隔离结果时可以先创建新 Map 再填入条目,但其中对象键和值仍可能共享引用。应根据实际修改层级决定复制范围,避免把新集合容器误当成整个状态树都已独立。
中途失败不会撤销前面成功的修改
例如第二次 set 的参数求值调用业务函数并抛错,第一次 set 可能已经完成,原集合会留下部分结果。若需要全部成功后才对外可见,应先校验或在临时集合中构建,再一次替换发布。
自行设计链式 API 时也应说明可变性和失败行为。过长链条若混入条件分支、异步操作或状态返回值,往往不如命名步骤容易定位错误,简洁不应牺牲操作完成点的可见性。
容易答错的地方
- 认为链式 set 每次产生新 Map
- 返回身份与原对象相同,修改会立刻作用于共享容器。需要不可变状态时应显式创建新实例并保留正确元素语义,不能仅凭赋给新变量就认为旧集合未变。
- 把 delete 接在链条中后继续 set
- delete 返回布尔值,后续 set 不再是在 Map 上调用。应先完成删除并检查结果,再单独执行下一步,或者设计清楚的包装函数,避免忽略本来有意义的操作状态。
面试官还会怎么问?
Set.add 重复值后 size 会增加吗?
不会,相等成员已经存在时仍返回原 Set,但不新增成员。是否支持链式与是否实际改变数量是两个问题,调用方不能通过返回实例判断这次有没有新增条目。
new Map(oldMap) 后值对象独立吗?
不会自动深复制,条目中的键和值通常仍保留相同引用。新容器可以隔离增删关系,但内部对象修改仍可能共享,应按业务更新路径决定是否还要复制具体值。
链式调用能自动处理异步 set 包装吗?
若包装函数返回 Promise,后续接到的是 Promise 而不是集合,必须按异步契约等待。API 命名相同也不代表返回类型相同,应查看实际包装接口并明确错误传播与完成条件。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。