作者:程序员成长指北
原文:mp.weixin.qq.com/s/WuZlo_92q...
最近小组里的小伙伴,暂且叫小A吧,问了一个bug:
提示数据循环引用,相信不少小伙伴都遇到过类似问题,于是我问他:
我:你知道问题报错的点在哪儿吗
小A: 知道,就是下面这个代码,但不知道怎么解决。
ini
onst a = {};
const b = { parent: a };
a.child = b; // 形成循环引用
try {
const clone = JSON.parse(JSON.stringify(a));
} catch (error) {
console.error('Error:', error.message); // 会报错:Converting circular structure to JSON
}
上面是我将小A的业务代码提炼为简单示例,方便阅读。
- 这里
a.child
指向b
,而b.parent
又指回a
,形成了循环引用。 - 用
JSON.stringify
时会抛出 Converting circular structure to JSON 的错误。
我顺手查了一下小A项目里 JSON.parse(JSON.stringify())
的使用情况:

一看有50多处都使用了, 使用频率相当高了。
我继续提问:
我:你有找解决方案吗?
小A: 我看网上说可以自己实现一个递归来解决,但是我不太会实现
于是我帮他实现了一版简单的递归深拷贝:
ini
function deepClone(obj, hash = new Map()) {
if (typeof obj !== 'object' || obj === null) return obj;
if (hash.has(obj)) return hash.get(obj);
const clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
return clone;
}
// 测试
const a = {};
const b = { parent: a };
a.child = b;
const clone = deepClone(a);
console.log(clone.child.parent === clone); // true
此时,为了给他拓展一下,我顺势抛出新问题:
我: 你知道原生Web API 现在已经提供了一个深拷贝 API吗?
小A:???
于是我详细介绍了一下:
主角 structuredClone
登场
structuredClone()
是浏览器原生提供的 深拷贝 API,可以完整复制几乎所有常见类型的数据,包括复杂的嵌套对象、数组、Map、Set、Date、正则表达式、甚至是循环引用。
它遵循的标准是:HTML Living Standard - Structured Clone Algorithm(结构化克隆算法)。
语法:
ini
const clone = structuredClone(value);
一行代码,优雅地解决刚才的问题:
ini
const a = {};
const b = { parent: a };
a.child = b; // 形成循环引用
const clone = structuredClone(a);
console.log(clone !== a); // true
console.log(clone.child !== b); // true
console.log(clone.child.parent === clone); // true,循环引用关系被保留
为什么增加 structuredClone
?
在 structuredClone
出现之前,常用的深拷贝方法有:
方法 | 是否支持函数/循环引用 | 是否支持特殊对象 |
---|---|---|
JSON.parse(JSON.stringify(obj)) |
❌ 不支持函数、循环引用 | ❌ 丢失 Date 、RegExp 、Map 、Set |
第三方库 lodash.cloneDeep |
✅ 支持 | ✅ 支持,但体积大,速度较慢 |
手写递归 | ✅ 可支持 | ❌ 复杂、易出错 |
structuredClone
是 原生、极速、支持更多数据类型且无需额外依赖 的现代解决方案。
支持的数据类型
类型 | 支持 |
---|---|
Object | ✔️ |
Array | ✔️ |
Map / Set | ✔️ |
Date | ✔️ |
RegExp | ✔️ |
ArrayBuffer / TypedArray | ✔️ |
Blob / File / FileList | ✔️ |
ImageData / DOMException / MessagePort | ✔️ |
BigInt | ✔️ |
Symbol(保持引用) | ✔️ |
循环引用 | ✔️ |
❌ 不支持:
- 函数(Function)
- DOM 节点
- WeakMap、WeakSet
常见使用示例
1. 克隆普通对象
ini
const obj = { a: 1, b: { c: 2 } };
const clone = structuredClone(obj);
console.log(clone); // { a: 1, b: { c: 2 } }
console.log(clone !== obj); // true
2. 支持循环引用
ini
const obj = { name: 'Tom' };
obj.self = obj;
const clone = structuredClone(obj);
console.log(clone.self === clone); // true
3. 克隆 Map、Set、Date、RegExp
javascript
const complex = {
map: new Map([["key", "value"]]),
set: new Set([1, 2, 3]),
date: new Date(),
regex: /abc/gi
};
const clone = structuredClone(complex);
console.log(clone);
兼容性
提到新的API,肯定得考虑兼容性问题:

- Chrome 98+
- Firefox 94+
- Safari 15+
- Node.js 17+ (
global.structuredClone
)
如果需要兼容旧浏览器:
- 可以降级使用
lodash.cloneDeep
- 或使用 MessageChannel Hack
很多小伙伴一看到兼容性问题,可能心里就有些犹豫:
"新API虽然好,但旧浏览器怎么办?"
但技术的发展离不开新技术的应用和推广,只有更多人开始尝试并使用,才能让新API真正普及开来,最终成为主流。
建议:
如果你的项目运行在现代浏览器或 Node.js 环境,structuredClone
是目前最推荐的深拷贝方案 。 Node.js 17+:可以直接使用 global.structuredClone
。