前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库Map JSON.stringify 序列化
JavaScript数组与集合

JSON.stringify(Map) 为什么得到空对象,怎样可靠保存再恢复?

JSON.stringify 只序列化对象自己的可枚举属性,而 Map 的数据存在内部条目里,不是属性,所以得到 {}。保存时用 JSON.stringify([...map]) 转成条目数组,恢复时 new Map(JSON.parse(str)) 就行。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#数组与集合
先看核心答案读代码示例
理解线索

往返一次要过三关

  1. 编码[...map] 得到 [[key, value], ...] 形式的条目数组
  2. 值能不能进 JSONundefined、NaN、BigInt、函数都有损失或直接报错
  3. 恢复JSON.parse 得到数组,再交给 new Map 才变回 Map

键是对象时,JSON 能把它的字段存下来,但恢复出来的是一个新对象。拿原来那个对象的引用去 get,永远查不到。

核心回答

先记住这个答案

JSON.stringify 序列化对象时,只看它自己的可枚举字符串属性。Map 的数据存在内部的条目里,不是属性,所以直接序列化只能得到 {},数据悄悄丢了。正确做法是先转成条目数组:JSON.stringify([...map]),恢复时 new Map(JSON.parse(str))。这个方法保留了键的类型和插入顺序,但仍然受 JSON 本身的限制:键是对象时恢复出来的是新对象,undefined、NaN 这些值会变成 null,遇到 BigInt 还会直接报错。

  • Map 的条目不是属性,JSON.stringify 看不见
  • 保存转成 [...map],恢复时交给 new Map
  • 对象键恢复后是新对象,原来的引用查不到
  • Object.fromEntries 会把键都转成字符串

条目数组的保存和恢复

比如把各状态的任务数量存进 localStorage,下次打开页面再恢复。

转成条目数组再还原JavaScript
const source = new Map([['draft', 2], ['ready', 5]])

console.log(JSON.stringify(source))                // 直接序列化,数据丢了

const encoded = JSON.stringify([...source])        // 转成条目数组
console.log(encoded)

const restored = new Map(JSON.parse(encoded))      // parse 得到数组,再建 Map
console.log(restored.get('ready'))
console.log(JSON.stringify([...restored.keys()]))  // 顺序也保留了
查看输出与解释
{}
[["draft",2],["ready",5]]
5
["draft","ready"]

[...source] 展开得到的是 [['draft', 2], ['ready', 5]] 这样的二维数组,每一项是一对键值,JSON 能完整表示。JSON.parse 还原出来的只是普通数组,new Map 接收条目数组,才把它变回 Map。 也可以写一个 replacer 和 reviver 交给 JSON.stringify 和 JSON.parse,遇到 Map 时自动转成 { dataType: 'Map', value: [...] } 这样带标记的结构,适合 Map 嵌套在大对象里的情况。

为什么不推荐 Object.fromEntries

另一种常见写法是 JSON.stringify(Object.fromEntries(map)),把 Map 转成普通对象。键都是普通字符串时没问题,但它有两个坑。

键类型被抹平
对象的键只能是字符串或 Symbol,数字 1 和字符串 '1' 转成对象后都变成属性 "1",后一个会覆盖前一个。键是对象时更糟,都会变成 "[object Object]"。另外对象会把整数键按数值大小重新排序,Map 原来的插入顺序也丢了。 条目数组没有这些问题,数字键恢复后还是数字,所以默认用 [...map] 更稳。
回答前,多想一步

容易答错的地方

看到 {} 以为 Map 本来是空的
这是很隐蔽的问题:Map 里明明有数据,存进 localStorage 后再读出来就没了,也不报错。判断 Map 是否为空要看 map.size,不能看序列化结果。
以为条目数组能无损保存任何 Map
条目数组只解决了「条目看不见」的问题,JSON 本身的限制还在。值里有 Date 会变成字符串,有 undefined 在数组里会变成 null,有 BigInt 会抛 TypeError,有循环引用也会报错。存之前要清楚数据里有哪些类型。
试着用自己的话回答

面试官还会怎么问?

键是对象的 Map,恢复后为什么查不到?

Map 按引用比较对象键。恢复出来的键是 JSON.parse 新建的对象,字段一样但不是同一个引用,拿原对象去 get 当然查不到。跨页面、跨接口保存时,应该用稳定的 id 当键,而不是对象本身。

可以给 Map.prototype 加一个 toJSON 吗?

技术上可以,JSON.stringify 遇到有 toJSON 方法的对象会先调用它。但这会影响项目里所有 Map 的序列化,还可能和第三方库冲突。更好的做法是写一个显式的转换函数,或者只在需要的地方传 replacer。

条目数组里有重复的键,恢复后会怎样?

new Map 按顺序逐个 set,后面的值会覆盖前面的,但键的位置保留第一次出现的位置。数据来自外部时,如果重复键意味着数据有问题,应该在恢复前检查,而不是静默覆盖。

从一道题,走向一组知识

把知识连起来

数组与集合

Map 的遍历顺序是怎么定的,和对象的属性顺序有什么区别?

条目数组能原样保留 Map 的插入顺序,转成普通对象则可能被整数键规则重新排序。

数组与集合

用普通对象当字典时为什么会读到原型属性,Map 能避免吗?

Map 的条目和它身上的普通属性是两套数据,这正是 JSON.stringify 看不到条目的原因。

参考资料

  • MDN:Map 与 JSON
  • MDN:JSON.stringify

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 条目数组的保存和恢复
  3. 为什么不推荐 Object.fromEntries
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑