在前端开发中,我们常常遇到需要对一个对象进行深度复制(deep clone)或者将其转为“纯数据”格式后再进行处理的场景。将对象经过 JSON.stringify() 序列化为字符串,再通过 JSON.parse() 反序列化回对象,乍看似乎多此一举,事实上却为我们提供了以下关键能力:一是实现对嵌套对象的深度复制,二是剥离原型链与方法,确保得到纯粹的 JSON 数据结构,三是便于跨环境(如跨 iframe、前后端通信、本地存储)传输时避免循环引用或非序列化数据导致的错误。下面将分几个方面,结合真实案例,严谨地探讨该技巧的原理、优势、局限与替代方案。

JSON.stringify() 与 JSON.parse() 方法概述

在深入分析前,有必要明确两者的基础功能。 JSON.stringify() 静态方法可将一个 JavaScript 值(对象、数组、基本类型等)序列化成 JSON 字符串,序列化过程中如遇 undefined、函数或 Symbol,将被丢弃或转换为 null (MDN Web Docs)。 与之相对,JSON.parse() 方法可将符合 JSON 语法的字符串解析为 JavaScript 对象或值,并可选地通过 reviver 函数对结果做二次转换 (MDN Web Docs)。

两者相辅相成,共同构成了 JavaScript 与 JSON 数据交互的基础。

为什么先序列化再反序列化不是多此一举?

实现深度克隆:打破引用联系

当一个对象中包含嵌套对象时,若仅使用浅拷贝(如扩展运算符或 Object.assign()),仅能复制第一层属性,内部对象仍然保持对原对象的引用。 将对象先 stringifyparse,可创建一个新的对象树,其中所有嵌套均被全新分配,彻底脱离原始对象的引用关系,从而实现深度克隆 (Stack Overflow)。

const original = { user: { name: "张三" } };
const clone = JSON.parse(JSON.stringify(original));
clone.user.name = "李四";
// original.user.name 依旧是 "张三",两者互不影响

该方法简单直观,常见于需要快速制作纯数据副本的场景。

剥离原型与方法:净化为纯数据对象

JavaScript 对象可能携带原型链和方法,某些框架功能或序列化场景(如发送到后端)只接受纯 JSON 数据,此时调用 stringifyparse 可自动去除原型及方法,只保留可安全序列化的属性,如数字、字符串、布尔值、嵌套对象、数组等 (Amit Merchant)。

class Person {
  constructor(name) { this.name = name; }
  greet() { console.log("Hello " + this.name); }
}
const p = new Person("Alice");
const plain = JSON.parse(JSON.stringify(p));
// plain 已不带 greet 方法,仅为 { name: "Alice" }

这一净化过程在跨平台、跨语言通信中极其有用。

解决跨环境传输与存储时的循环引用与非序列化数据问题

部分浏览器存储(如 localStorage)、前后端 API 调用、或者跨 iframe 传值时,都要求数据可序列化为 JSON。 使用 JSON.stringify 会在序列化时抛出循环引用异常,如果使用该配对方法,通常会在代码层面先捕获并处理循环引用,或明确避开循环结构,同时确保输出符合 JSON 格式。 对于某些框架(如 SAP UI5 的 OData 绑定模型),可能需要将模型对象转换为纯 JSON,以解除与框架内部状态管理的耦合,避免潜在的内存泄露与更新冲突 (GeeksforGeeks)。

真实案例:SAP UI5 中的应用场景

在 SAP UI5 的开发中,oData 对象往往是从后台 OData 服务获取的模型实例,携带大量框架内部方法、元数据及绑定信息。 当我们需要对原始数据做二次加工或传递给第三方组件时,直接操作模型实例可能会触发双向绑定或生命周期钩子,造成难以预料的更新。 此时,JSON.parse(JSON.stringify(oData)) 能快速产出一个脱离绑定的纯净 JSON 对象,便于自由修改、格式化或者发送到另一端而不干扰原始模型 (Built In)。

// 从 ODataModel 获取的绑定上下文
const oContextData = this.getView().getModel().getData();
// 深度克隆为纯数据
const plainData = JSON.parse(JSON.stringify(oContextData));
// 对 plainData 自由操作,不会影响 UI 绑定

限制与替代方案

虽然 JSON.stringifyJSON.parse 技巧简洁易用,但也存在以下局限或副作用:

丢失特殊数据类型

  • undefinedSymbol、函数均无法被序列化,会被忽略或转换为 null (Medium)。

  • Date 对象会被转为 ISO 字符串,反序列化后不再是原生 Date 实例 (Medium)。

  • MapSetTypedArray 等复杂结构无法正确克隆 (DEV Community)。

性能开销

对于大型对象或数据量巨大的场景,字符串化与解析的过程会占用较多时间与内存,不宜频繁调用 (DEV Community)。

现代替代方案:structuredClone()

2022 年后,浏览器原生提供了 structuredClone() 全局方法,可高效地实现深度克隆,并支持循环引用、DateMapSetTypedArrayArrayBuffer 等多种类型,同时性能优于 JSON 技巧 (Medium)。

const clone = structuredClone(complexObject);
// 保留原有类型,且无循环引用报错

在不需要纯 JSON 格式的场景下,推荐优先使用 structuredClone(),兼顾性能与数据完整性。

结语

将对象先序列化为 JSON 字符串再反序列化,虽看似多此一举,却为我们在深度克隆、数据净化、跨环境传输等场景下提供了简洁而可靠的手段。在选择该技巧时,需要权衡数据类型兼容性与性能开销;对于更复杂或大型的数据结构,structuredClone() 则是更佳的现代化方案。通过深入理解两者的内在机制,我们才能在实际项目中游刃有余,选择最适合的复制与序列化策略。