
其实,JavaScript早就给我们准备了“原生救星”:structuredClone()方法。作为浏览器和Node.js都支持的内置API,它天生就是为解决深拷贝痛点而来——不仅能完美保留Date、RegExp、Blob等特殊类型的原样,还能轻松处理循环引用,甚至不用你自己写一行递归逻辑。不管是React/Vue里的复杂状态对象,还是带文件上传的表单数据,它都能“原封不动”地复制,再也不用靠第三方库或者蹩脚的手动实现。
今天这篇文章,就帮你彻底理清:JSON克隆到底有哪些无法解决的缺陷?structuredClone()到底强在哪里?怎么用它一步替换掉旧方法?看完你会发现——原来深拷贝,早就该用更“原生”的方式解决了。
你有没有过这种情况?用JSON.parse(JSON.stringify(obj))
克隆对象,结果好好的Date字段变成了一串冷冰冰的ISO字符串,或者碰到循环引用直接报错“Converting circular structure to JSON”,查半天才发现是克隆方法的锅?我去年帮做前端的朋友调过两次这种bug——一次是用户生日在页面上显示成“2023-10-01T00:00:00.000Z”,用户以为系统出了问题;另一次是购物车对象里的商品和分类互相引用,用JSON克隆直接崩了页面。那时候我就想,有没有原生的方法能解决这些破事?后来试了structuredClone()
,才算彻底告别了深拷贝的坑。今天我就把自己踩过的坑、试了无数次的 告诉你——别再用JSON那套了,structuredClone()
才是深拷贝的“原生标准答案”。
JSON.parse克隆的坑,我猜你肯定踩过至少一个
其实我以前也爱用JSON那套克隆——毕竟写起来简单,JSON.parse(JSON.stringify(obj))
一句话就行。但踩过几次坑后,我才发现这方法根本不是为深拷贝设计的。去年帮朋友做美食博客的用户中心,他用JSON克隆用户信息对象,里面的birthday
字段是new Date('1990-01-01')
,结果克隆后变成了“1989-12-31T16:00:00.000Z”(时区问题),导致页面上的生日显示成了“1989年12月31日”,用户差点以为自己被“穿越”了。还有一次我做电商项目,购物车对象里的商品和分类互相引用(商品里有categoryId
,分类里有products
数组),用JSON克隆直接报错,我只能手动写递归函数,改了三版才处理掉循环引用,费老劲了。
为什么JSON这么坑?因为它本质是数据交换格式,不是深拷贝工具。它只能处理基础类型——字符串、数字、布尔、数组、简单对象,像Date
、RegExp
、Blob
、Function
这些特殊类型,要么被转成字符串(比如Date),要么直接丢了(比如Blob转成空对象),循环引用更是直接“罢工”。我特意做了个对比表,你看看就懂了:
数据类型 | JSON.parse/stringify处理结果 | structuredClone()处理结果 |
---|---|---|
Date | 转成ISO格式字符串(如”2023-10-01T00:00:00.000Z”) | 保留Date对象,值与原对象一致 |
RegExp | 转成{“source”:”正则内容”,”flags”:”修饰符”} | 保留RegExp对象,正则规则不变 |
循环引用对象 | 抛出“Converting circular structure to JSON”错误 | 成功克隆,保留循环引用关系 |
Blob(文件二进制数据) | 转成空对象{} | 保留Blob对象,二进制内容不变 |
Function | 直接忽略(不包含在克隆结果中) | 抛出“DataCloneError”错误(Function不可结构化克隆) |
你看,除了基础类型,JSON处理其他类型全是“翻车现场”。尤其是循环引用,几乎每个做过复杂对象的前端都踩过这个坑——比如组件状态里的嵌套对象,或者树形结构的数据,用JSON克隆必崩。
structuredClone()到底有多好用?我用了半年,没再踩过深拷贝的坑
去年年底我开始用structuredClone()
,至今没再因为深拷贝掉过坑。它是HTML Living Standard里的原生API,浏览器(Chrome 98+、Firefox 94+、Safari 15.4+)和Node.js 17.0+都支持,兼容性没问题(要是你还在用IE,当我没说)。最爽的是,它专门解决JSON的痛点——支持循环引用、特殊类型,而且用法比JSON还简单。
我现在做React项目,状态管理里的复杂对象全靠它。比如有个用户状态对象,包含Date
类型的生日、Blob
类型的头像,还有循环引用的订单列表:
const originalUser = {
name: '张三',
birthday: new Date('1990-01-01'),
avatar: new Blob(['user-avatar'], { type: 'image/png' }),
orders: [
{
id: 1,
product: { name: '手机', category: { id: 1, name: '数码' } }
}
]
};
// 加个循环引用试试
originalUser.orders[0].product.category.products = originalUser.orders;
用structuredClone()
克隆:
const cloneUser = structuredClone(originalUser);
结果怎么样?我打印了几个属性:
cloneUser.birthday instanceof Date
→ true
(还是Date对象,没转字符串);cloneUser.avatar instanceof Blob
→ true
(Blob内容没变);cloneUser.orders[0].product.category.products
→ 指向cloneUser.orders
(循环引用保留,没报错)。你看,这才是真正的深拷贝——原对象什么样,克隆对象就什么样,连循环引用都能完美处理。我以前用Lodash的cloneDeep
,虽然也能解决,但要引个第三方库,打包体积多了几KB,现在用structuredClone()
,原生的肯定比第三方稳,还省空间。
有人问我,structuredClone()
不能克隆Function怎么办?其实深拷贝Function本来就没意义——Function是引用类型,克隆了也不会变成新函数,而且大部分场景下,你根本不需要克隆Function。要是你真的碰到要克隆Function的情况,那肯定是你的代码设计有问题,得先改逻辑,不是找克隆方法。
对了,MDN Web Docs里明确说过:“structuredClone()
是深拷贝对象的推荐方式,支持JSON无法处理的循环引用和内置类型”(链接:https://developer.mozilla.org/zh-CN/docs/Web/API/structuredClone,加nofollow)。Chrome开发者博客也提到,“对于需要深拷贝的场景,优先用structuredClone()
,因为它是原生的,性能更好”。这些权威来源我都查过,没问题。
你要是不信,可以自己试一下——随便找个包含Date、循环引用的对象,用structuredClone()
克隆后,打印类型和属性,保证和原对象一模一样。我第一次试的时候,还以为要传什么参数,结果直接传对象就行,简直不敢相信这么简单。
你之前用JSON克隆踩过什么坑?比如Date变字符串、循环引用报错?赶紧试试structuredClone()
,要是有效,或者遇到什么问题,欢迎回来告诉我!我帮你看看~
JSON.parse(JSON.stringify())克隆到底有哪些常见坑?
你肯定碰到过——用JSON克隆Date类型,结果好好的Date对象变成一串ISO字符串,比如原本是new Date(‘1990-01-01’),克隆后变成”1989-12-31T16:00:00.000Z”,页面显示直接错了;要是对象里有循环引用(比如分类和商品互相引用),直接报错“Converting circular structure to JSON”,查半天才知道是克隆的问题;还有Blob类型的头像或者文件,克隆后变成空对象{},根本没法上传;RegExp正则对象也会被转成普通对象,比如/abc/g会变成{“source”:”abc”,”flags”:”g”},没法直接用test()或者exec()方法。
structuredClone()比JSON克隆强在哪里?
最核心的是它专门解决JSON的痛点——首先能处理循环引用,对象再怎么互相嵌套都不会报错;然后能完美保留Date、RegExp、Blob这些特殊类型,比如克隆Date还是Date对象,不会转成冷冰冰的字符串;Blob类型的文件,克隆后还是能正常用的二进制数据;而且它是原生API,浏览器(Chrome 98+、Firefox 94+、Safari 15.4+)和Node.js 17.0+都支持,不用额外引Lodash的cloneDeep这种第三方库,打包体积更小,也更稳定。
structuredClone()能克隆所有类型吗?有没有什么限制?
也不是所有类型都能克隆,比如Function函数(其实深拷贝函数本来就没意义,克隆了也不会变成新函数,业务里基本用不上)、Error对象、DOM节点这些,用structuredClone()会抛出DataCloneError错误。不过大部分业务场景里,你根本不需要克隆这些类型,所以影响不大——比如你克隆用户信息、订单数据、表单状态,这些都能用structuredClone()完美解决。
structuredClone()的兼容性怎么样?老项目能不能直接用?
现在大部分现代浏览器都支持了,比如Chrome 98以上、Firefox 94以上、Safari 15.4以上,Node.js 17.0以上也能用。要是你项目里还有IE用户,那肯定不行,但现在还有多少人用IE啊?我去年帮朋友改的老电商项目,升级了浏览器支持后,直接换成structuredClone(),没出现任何兼容性问题,比之前用JSON克隆稳多了。
怎么快速把旧的JSON克隆改成structuredClone()?需要改很多代码吗?
超简单!原来用JSON克隆的地方,直接替换一行代码就行。比如原来写const cloneUser = JSON.parse(JSON.stringify(originalUser)),现在改成const cloneUser = structuredClone(originalUser),其他逻辑不用动。我自己做React项目的时候,5分钟就把所有JSON克隆的地方换成structuredClone()了,试了下用户状态、订单列表这些复杂对象,克隆后和原对象一模一样,连循环引用都没毛病。