先记住这个答案
JSON.stringify 按普通对象的可枚举自有字符串属性等规则序列化,Map 的内部条目不属于这些属性,所以普通 Map 默认常得到 {}。可以把条目显式转成数组后编码,再用 new Map 恢复,但这只对约定的可序列化键和值成立。对象键经过 JSON 会变成新对象,身份无法保持;undefined、NaN、Symbol、BigInt 等也有丢失或失败边界。Object.fromEntries 会把键转成对象属性键,可能合并原本不同的键。对外格式应定义类型、结构、版本和冲突策略,而非声称任意 Map 都能无损往返。
- 内部条目不会默认当成对象属性输出
- 条目数组保留顺序,但不能保留任意身份和类型
- 恢复前校验数据结构及重复键规则
先把集合条目转换为显式数据
下面 Map 使用稳定字符串键和普通数字,展开后得到二元条目数组,JSON 可以保留这组受限数据。解析后得到的是数组,调用 new Map 才恢复集合操作,不是 JSON.parse 自动识别了 Map。
输出还验证恢复集合的查询与条目顺序。这个例子没有附加普通属性或自定义 toJSON,因此默认 stringify 得到空对象;如果自行添加这些行为,输出自然可能不同。
const source = new Map([['draft', 2], ['ready', 5]]);
console.log(JSON.stringify(source));
const encoded = JSON.stringify([...source.entries()]);
console.log(encoded);
const restored = new Map(JSON.parse(encoded));
console.log(restored.get('ready'));
console.log(JSON.stringify([...restored.keys()]));查看输出与解释
{}
[["draft",2],["ready",5]]
5
["draft","ready"]条目数组在这个限定值域内保留键和值,恢复后仍按原顺序迭代。真正接收外部 JSON 时还应验证解析结果是合格二元条目数组,示例使用自己刚编码的受控输入。
对象键和特殊值会丢失原本语义
对象键序列化后再解析得到新对象,因此用原对象引用查询恢复 Map 不会命中。若业务需要跨进程关联对象,应使用稳定标识并通过独立表恢复关系,而不是尝试保留内存地址式身份。
数组中的 undefined、NaN 等可能变成 null,Symbol 也无法作为普通 JSON 值无损保留,BigInt 默认会导致失败。循环引用同样需要额外处理,应先限定数据类型,再选择显式编码,而非捕获错误后返回空 Map。
恢复过程要定义冲突与信任边界
外部条目数组可能形状错误、过大或含重复键,new Map 会按自身插入规则处理,但这不一定符合业务要求。应校验每项长度、键值类型和数量,并决定重复键是拒绝还是按明确规则覆盖。
如果使用 Object.fromEntries,中间对象键可能把数字一与字符串一合并到同一属性,破坏原 Map 区分。需要保留不同键类型时应选择带类型信息的格式,并对版本演进和未知字段做明确处理。
容易答错的地方
- 看到空对象就以为 Map 本来没有数据
- 默认 JSON 枚举不到内部条目,不能据输出判断集合是否为空。应使用 size 或条目 API 检查集合,再选择明确序列化格式,避免把真实缓存内容在保存时悄悄丢掉。
- 认为条目数组可以无损保存任何 Map
- JSON 的值域和对象身份限制仍然存在,展开只解决条目可见性。必须列出允许类型、特殊值编码与恢复校验,不能把一个字符串键示例推广到任意对象图。
面试官还会怎么问?
对象键恢复后用原引用为什么查不到?
JSON 创建的是新的对象,Map 对象键按身份比较,字段内容相同也不代表相同键。跨边界关联应使用稳定业务 id,或按明确引用表协议恢复关系,不能依赖原内存对象继续匹配。
可以全局改 Map.prototype.toJSON 吗?
技术上可以改变序列化行为,但会影响所有相关代码并引入隐式格式约定。通常更清楚的是显式编码函数或局部 replacer,并为格式和恢复策略提供测试,避免全局副作用。
重复键数组恢复时谁会留下?
后续相等键会更新已有条目的值,位置规则仍由 Map 决定。外部协议是否允许这种覆盖应明确规定;若重复代表数据损坏,就应在构造之前检查并拒绝,而不是静默接受。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。