简介:还在用JSON.parse(JSON.stringify())做深拷贝?小心Date变字符串、函数丢失、循环引用导致页面白屏!本文深度解析JSON深拷贝的致命缺陷,并带你掌握原生深拷贝神器structuredClone,轻松搞定99%的前端数据拷贝需求,告别线上事故。
上周同事遇到一个神坑问题,页面数据改了,结果原始数据也跟着变了,找了半天原因,最后发现是深拷贝没写对。他用的就是那个号称“一行代码搞定深拷贝”的写法:
const newData = JSON.parse(JSON.stringify(oldData))
你是不是也觉得这玩意儿挺好用的?我以前也这么觉得,直到它差点把我的项目搞崩了。
咱们先看一个最简单的例子。假设你有个订单数据,里面有个下单时间:
const order = { id: 1001, price: 299, createTime: new Date('2025-06-01')}
const copy = JSON.parse(JSON.stringify(order))console.log(copy.createTime) // 猜猜输出什么?
你以为是 Date 对象?太天真了。输出的是 "2025-06-01T00:00:00.000Z",一个字符串!
这意味着你后面如果想调 getMonth()、getFullYear() 这些日期方法,直接报错给你看。
更离谱的事情还在后面
再看个例子,你有一个配置对象,里面有个重置函数:
const config = {
theme: 'dark',
reset: function() {
this.theme = 'light'
},
log: () => console.log('config loaded')
}
const copy = JSON.parse(JSON.stringify(config));
console.log(copy) // 输出:{ theme: "dark" }
什么?reset 和 log 直接消失了?没错,函数会被无情丢弃。
那 undefined 呢?
const data = { name: '阿浪', age: undefined, hobby: null}
const copy = JSON.parse(JSON.stringify(data));
console.log(copy) // { name: "阿浪", hobby: null }
age 这个字段整个被删掉了,连个招呼都不打。
还有 Symbol 类型的值,也一样会被直接忽略。
大多数人在用这个方法时,压根不知道它还有个“定时炸弹”——循环引用。
啥是循环引用?就是对象里的某个属性,直接或间接地引用了自己。
const me = { name: '阿浪', friend: null}
me.friend = me // 自己引用自己
// 这一行会直接报错!
const copy = JSON.parse(JSON.stringify(me));
// Uncaught TypeError: Converting circular structure to JSON
项目里如果出现了循环引用(比如树形结构、双向链表、父子组件传参),整个页面就会直接崩溃。而你在报错堆栈里看到的,只有一行尴尬的 JSON.stringify
我当时就是因为这个,线上出了事故——后台返回的数据里有个递归引用,我随手用 JSON.parse(JSON.stringify(...)) 一拷,页面白屏了。用户反馈说“点一下就没了”,那感觉,啧,懂的都懂。
先别急着说自己写一个递归函数,太麻烦了,而且还有类型判断的各种坑(比如数组和对象要区分,正则要特殊处理)。
其实浏览器早就给我们准备了一个原生深拷贝方法,名字很直白——structuredClone(结构化克隆)。
const original = {
name: '阿浪',
birthday: new Date('1995-03-15'),
tags: ['前端', '老狗'],
// 处理循环引用!
self: null
}
original.self = original
const copy = structuredClone(original);
console.log(copy.birthday instanceof Date);
// true,还是Date对象!
console.log(copy.self === copy);
// true,循环引用也完美保留
你看,Date 对象保住了,Map、Set、RegExp 甚至 ArrayBuffer 都能正确拷贝。而且它原生支持循环引用,再也不会崩溃了。
我们来和 JSON 方案做个对比
| 特性 | JSON.parse(JSON.stringify(...)) | structuredClone |
|---|---|---|
| Date 对象 | ❌ 变成字符串 | ✅ 保留为 Date |
| 函数 / undefined / Symbol | ❌ 直接丢失 | ⚠️ 会抛出异常(无法拷贝) |
| Map / Set / RegExp | ❌ 变成空对象或丢失 | ✅ 正确拷贝 |
| 循环引用 | ❌ 报错崩溃 | ✅ 支持 |
| 性能 | 一般 | 较好(底层原生实现) |
注意:structuredClone 也不是万能的。它不能拷贝函数、不能拷贝 DOM 元素、不能拷贝某些内置对象(比如 Error、Promise)。遇到这些情况,它也会抛错。
不过呢,在 99% 的前端业务场景里——拷贝后端返回的 JSON 数据、拷贝组件 state、拷贝配置对象——它已经完全够用了。
structuredClone 是 2022 年左右全面支持的,现在主流浏览器(Chrome 98+、Firefox 94+、Safari 15.4+、Edge 98+)都已经原生支持了。Node.js 从 17.0.0 开始也支持(需要 --experimental-global 标志,后来直接内置)。
如果你还在兼容 IE 或者老版本安卓 WebView,那没办法,只能用 lodash 的 _.cloneDeep 或者自己造轮子了。但说真的,2026 年还死磕 IE 的项目,应该不多了吧?