前端开发中,序列化(Serialization)和反序列化(Deserialization)是数据交互的关键环节。
JSON.stringify()和JSON.parse()是最常用的方法,但存在数据类型丢失(Date变为字符串)、循环引用报错等问题。
解决方案包括使用自定义replacer/reviver函数、structuredClone()处理复杂对象,以及第三方库处理二进制数据。
针对不同场景:
- API交互推荐JSON
- 表单提交用FormData
- 高性能传输考虑MessagePack
工程实践中需注意特殊类型处理、安全防御和显式转换。
通过规范化处理可避免数据格式导致的bug。
表单相关:序列化、状态持久化、复杂数据处理(附:Object.fromEntries()方法 函数式对象 解释)
Worker 对象 与 DedicatedWorkerGlobalScope 实例对比(附:序列化、结构化克隆算法、循环引用 解释)
前端序列化和反序列化总结
在前端开发中,序列化(Serialization) 和**反序列化(Deserialization)**是数据交互的核心环节,主要用于数据在网络传输、本地存储(如 localStorage/IndexedDB)以及状态管理中的格式转换。
1、核心概念
- 序列化 (Serialization) :
- 定义 :将 JavaScript 对象、数组等复杂数据结构转换为字符串(通常是 JSON 格式)或二进制流的过程。
- 目的:以便通过网络传输(HTTP 请求体)、持久化存储(存入 localStorage/数据库)或在不同进程间传递。
- 前端典型操作 :
JSON.stringify()。
- 反序列化 (Deserialization) :
- 定义:将字符串(如 JSON 字符串)或二进制流还原为内存中的 JavaScript 对象或数据结构的过程。
- 目的:解析后端返回的数据、读取本地存储的数据,使其在代码中可操作。
- 前端典型操作 :
JSON.parse()。
2、前端常用序列化与反序列化方法的对比表格:
| 方法 / 场景 | 序列化 (对象 → 传输/存储格式) | 反序列化 (传输/存储格式 → 对象) | 主要特点 | 适用场景 | 注意事项 |
|---|---|---|---|---|---|
| JSON | JSON.stringify() |
JSON.parse() |
• 通用性强 • 可读性好 • 支持嵌套结构 | • API 数据传输 • 本地存储 • 配置文件 | • 不支持函数、undefined、Symbol、循环引用 • 日期对象会转为字符串 |
| FormData | new FormData() + append() |
手动构建对象 / Object.fromEntries() |
• 专用于表单数据 • 支持文件上传 | • 文件上传 • 复杂表单提交 | • 不支持嵌套结构(除非手动处理) • 不能直接转为 JSON |
| URLSearchParams | new URLSearchParams() |
Object.fromEntries() / get() |
• 生成 query string • 符合 URL 规范 | • GET 请求参数拼接 • 表单 URL 编码提交 | • 不支持嵌套对象 • 值默认转为字符串 |
| Blob / ArrayBuffer | Blob() / FileReader |
FileReader / 解码方法 |
• 二进制数据处理 | • 文件处理 • 图像/音视频数据 • WebSocket 二进制传输 | • 需要理解编码方式 • 通常配合 FileReader 或 Response 使用 |
| structuredClone | structuredClone() |
structuredClone() |
• 深度克隆 • 支持更多数据类型 | • 深拷贝对象 • 跨上下文数据传递 | • 不支持函数、DOM 节点 • 较新的浏览器 API |
| 自定义序列化 | 实现 toJSON 方法 |
手动解析 / 构造函数 | • 完全可控 • 满足特殊业务需求 | • 需要自定义序列化逻辑(如日期格式化、字段过滤) | • 需要手动维护序列化/反序列化一致性 |
| MessagePack | 第三方库 | 第三方库 | • 比 JSON 更小更快 • 二进制格式 | • 高性能数据传输 • 带宽敏感场景 | • 需要额外引入库 • 可读性差 |
| XML | DOMParser / 字符串模板 |
DOMParser / XMLSerializer |
• 结构严谨 • 支持命名空间 | • 遗留系统集成 • 需要严格文档结构的场景 | • 冗余信息多 • 解析相对复杂 |
快速选择指南
| 需求 | 推荐方法 |
|---|---|
| 普通 API 数据交换 | JSON |
| 表单提交(含文件) | FormData |
| URL 查询参数 | URLSearchParams |
| 文件/二进制数据处理 | Blob / ArrayBuffer |
| 深度克隆对象(含 Date、Map 等) | structuredClone |
| 高性能紧凑数据传输 | MessagePack(第三方库) |
| 旧系统 XML 接口 | DOMParser / XMLSerializer |
把数据流动的方向想象成 内存对象 (Object) ⇄ 字符串 (String)。
JSON.stringify()
- 记忆口诀 :Obj → String
- 逻辑:你要把复杂的对象"压扁"成一行文字才能传输或存储。
- 后缀含义 :
-ify(动词后缀,意为"使...变成")。
String+ify= 使变成字符串。- 类比:
Simplifying(简化),Terrifying(使恐怖)。- 场景 :存本地 (
localStorage)、发请求 (body)。
JSON.parse()
- 记忆口诀 :String → Obj
- 逻辑 :你拿到一串文字,需要把它"解析/拆开"还原成可用的对象。
- 单词含义 :
Parse(解析)。
- 就像语法分析一样,把句子拆解成结构。
- 场景 :读本地、收响应 (
response.json())。
一句话总结 :
想变字符串用-ify(Stringify),想还原对象用Parse。
快速自测表
| 你的目标 | 此时数据形态 | 目标形态 | 应该用哪个? | 记忆线索 |
|---|---|---|---|---|
| 存入 localStorage | 对象 {} |
字符串 "" |
JSON.stringify() |
-ify (变成字符串) |
| 发送 POST 请求 | 对象 {} |
字符串 "" |
JSON.stringify() |
打包 寄出 |
| 接收 API 响应 | 字符串 "" |
对象 {} |
JSON.parse() |
Parse (解析还原) |
| 读取 localStorage | 字符串 "" |
对象 {} |
JSON.parse() |
拆包 使用 |
| 打印日志调试 | 对象 {} |
字符串 "" |
JSON.stringify() |
为了看清结构转成文本 |
3、前端序列化的常见"坑"与解决方案
在使用原生的 JSON.stringify 和 JSON.parse 时,经常会遇到数据类型丢失的问题:
3.1 特殊类型丢失
- 问题 :
Date变成字符串,Map/Set变成空对象{},undefined/Function/Symbol被忽略,BigInt报错。 - 解决方案 :使用自定义的
replacer和reviver函数,或使用第三方库。
javascript
// 自定义序列化策略
const customStringify = (v) => JSON.stringify(v, (key, value) => {
if (value instanceof Map) {
return { __type: 'Map', value: [...value] };
}
if (value instanceof Date) {
return { __type: 'Date', value: value.toISOString() };
}
if (typeof value === 'bigint') {
return { __type: 'BigInt', value: value.toString() };
}
return value;
});
// 自定义反序列化策略
const customParse = (s) => JSON.parse(s, (key, value) => {
if (value?.__type === 'Map') {
return new Map(value.value);
}
if (value?.__type === 'Date') {
return new Date(value.value);
}
if (value?.__type === 'BigInt') {
return BigInt(value.value);
}
return value;
});
3.2 循环引用 (Circular Reference)
- 问题 :如果对象内部存在循环引用(A 指向 B,B 指向 A),
JSON.stringify会直接抛出错误TypeError: Converting circular structure to JSON。 - 解决方案 :
- 使用
flatted库(轻量级,兼容性好)。 - 使用
structuredClone()(现代浏览器原生支持,深度克隆且自动处理循环引用,但主要用于克隆,非严格意义的序列化字符串)。 - 自定义 replacer 记录已访问节点。
- 使用
3.3 安全性风险 (原型污染)
- 问题 :直接使用
JSON.parse解析不可信的用户输入可能导致原型污染(虽然比后端 Java/PHP 的反序列化漏洞轻微,但仍需注意)。 - 解决方案 :
- 永远不要信任前端接收到的来自用户直接输入的序列化数据。
- 使用
json-parse-better-errors等库提供更友好的错误提示。 - 对于复杂场景,考虑使用
superstruct或zod在反序列化后进行数据校验。
4. 现代替代方案与最佳实践
随着前端应用复杂度的提升,原生 JSON 已不能完全满足需求:
4.1 structuredClone() (推荐用于深拷贝/传输)
现代浏览器(2026年已完全普及)支持的结构化克隆算法。
- 优势 :原生支持
Date,Map,Set,RegExp,Blob,ImageBitmap等,自动处理循环引用。 - 局限 :它返回的是对象副本,不是字符串。若需存储或传输,仍需配合
postMessage(自动序列化) 或特定编码。 - 场景 :Web Worker 通信 (
postMessage底层即使用此算法)、深度克隆状态。
4.2 二进制序列化协议 (高性能场景)
对于数据量大、对带宽敏感的场景(如即时游戏、高频交易看板):
- Protocol Buffers (Protobuf) : Google 出品,强类型,体积极小。需
.proto文件定义结构。 - MessagePack: 类似 JSON 的二进制格式,无需预定义结构,兼容性好。
- FlatBuffers: 零拷贝解析,速度极快,适合超大数据集。
4.3 状态管理中的序列化
在 Redux, Pinia, Vuex 中:
- 持久化插件 (如
redux-persist,pinia-plugin-persistedstate):自动将 State 序列化存入localStorage。 - 注意:需配置白名单,避免将不可序列化的对象(如包含 DOM 节点或复杂类实例的状态)存入存储,否则重启应用时会崩溃。
5. 工程化建议总结
- 默认使用 JSON :90% 的业务场景(API 交互、配置存储)使用
JSON.stringify/parse即可,简单且通用。 - 处理特殊类型 :涉及
Date、Map等类型时,务必编写统一的transform工具函数,不要在业务代码中散乱处理。 - 大数据传输 :如果单次传输数据超过 100KB 且频率高,考虑切换到 MessagePack 或 Protobuf。
- Worker 通信 :利用
postMessage的结构化克隆能力,直接传递复杂对象,无需手动 JSON 转换。 - 安全防御:后端接口应严格控制 Content-Type,前端解析未知来源数据时需进行 Schema 校验(使用 Zod/Yup)。
- 避免隐式转换 :在将数据存入
localStorage前,显式调用JSON.stringify,取出时显式调用JSON.parse,不要依赖框架的隐式行为。
代码示例:通用的安全序列化工具类
TypeScript
// utils/serializer.ts
export const SafeSerializer = {
stringify: (obj: any): string => {
return JSON.stringify(obj, (key, value) => {
// 处理特殊类型
if (value instanceof Date) return { _type: 'date', val: value.toISOString() };
if (value instanceof Map) return { _type: 'map', val: Array.from(value.entries()) };
if (value instanceof Set) return { _type: 'set', val: Array.from(value) };
if (typeof value === 'bigint') return { _type: 'bigint', val: value.toString() };
// 过滤不可序列化的类型
if (typeof value === 'function' || typeof value === 'symbol' || value === undefined) {
return undefined;
}
return value;
});
},
parse: (str: string): any => {
try {
return JSON.parse(str, (key, value) => {
if (value?._type === 'date') return new Date(value.val);
if (value?._type === 'map') return new Map(value.val);
if (value?._type === 'set') return new Set(value.val);
if (value?._type === 'bigint') return BigInt(value.val);
return value;
});
} catch (e) {
console.error('反序列化失败:', e);
return null;
}
}
};
通过规范化的序列化处理,可以有效避免前端开发中因数据格式问题导致的难以排查的 Bug。