JS原生深拷贝的终极方案structuredClone用法代码示例
作者:代码里的小猫咪
前言
在 JavaScript 开发中,对象的深拷贝一直是一个经典痛点。从 JSON.parse(JSON.stringify()) 的 hack,到 Lodash 的 _.cloneDeep(),开发者们一直在找一个既可靠又高效的深拷贝。2022 年,structuredClone 作为全局 API 正式进入所有主流浏览器和 Node.js,终结了这场漫长的探索。
1. 基本用法
const original = {
name: 'Alice',
scores: [95, 87, 92],
metadata: {
createdAt: new Date(),
pattern: /hello/gi,
},
};
const cloned = structuredClone(original);
cloned.scores.push(100);
console.log(original.scores); // [95, 87, 92] — 互不影响
console.log(cloned.metadata.createdAt instanceof Date); // true — 类型保留
console.log(cloned.metadata.pattern instanceof RegExp); // true一行代码,深拷贝完成。没有序列化/反序列化的中间步骤,没有第三方依赖。
Transfer 参数
structuredClone 的第二个参数支持 transfer 选项,这是它相比其他深拷贝方案的独特能力:
const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
// 传统克隆:复制 100MB 内存
const cloned = structuredClone(buffer);
// Transfer:所有权转移,零拷贝
const transferred = structuredClone(buffer, { transfer: [buffer] });
console.log(buffer.byteLength); // 0
console.log(transferred.byteLength); // 104857600Transfer 的本质是所有权转移(ownership transfer),不是复制。底层只是修改了内存区域的归属指针,时间复杂度 O(1)。
为什么要这样设计?
核心原因是性能。如果要把 100MB 的视频帧数据发给 Web Worker 处理:
// 方式 1:复制(慢,瞬间多占 100MB 内存) worker.postMessage(buffer); // 方式 2:转移(瞬间完成,内存不增长) worker.postMessage(buffer, [buffer]); // 但此后主线程不能再用这个 buffer 了
Transfer 的代价是:原始变量变成一个"空壳"(规范称为 detached / neutered)。这是一个权衡——用"独占访问权"换来了"零拷贝性能"。
2. 支持/行为
这是 structuredClone 最大的优势所在。
- 支持:普通对象、数组、Date、Map、Set、RegExp、ArrayBuffer、TypedArray、Blob、File、BigInt、循环引用等
- 不支持/会报 DataCloneError(典型):
- function,函数闭包绑定了执行上下文,无法序列化
- WeakMap/WeakSet,弱引用语义不允许枚举
- Proxy(Vue reactive 就在这)
- DOM Nodes,绑定了渲染树,跨上下文无意义
- 保留对象图结构:可保留“同一引用关系”和循环结构
关键:原型链丢失
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
magnitude() {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
}
const v = new Vector(3, 4);
const cloned = structuredClone(v);
console.log(cloned.x); // 3
console.log(cloned.magnitude); // undefined
console.log(Object.getPrototypeOf(cloned) === Vector.prototype); // false
console.log(Object.getPrototypeOf(cloned) === Object.prototype); // truestructuredClone 克隆的是数据,不是行为。
对比 JSON.parse(JSON.stringify()) 支持/行为
- 支持:普通对象、数组、字符串、数字、布尔、null
- 会丢失/变化:
- undefined(对象属性会被丢弃)
- function(丢弃)
- symbol(丢弃)
- Date -> 字符串(ISO)
- Map/Set -> 通常变空对象或丢信息
- RegExp -> 普通对象/空对象
- NaN/Infinity/-Infinity -> null
- BigInt -> 直接报错(TypeError)
- 循环引用:直接报错 Converting circular structure to JSON
循环引用
这是 structuredClone 相比 JSON 方案的核心优势之一:
const obj = { name: 'root' };
obj.self = obj;
obj.children = [{ parent: obj }];
// JSON 方案直接爆炸
// JSON.stringify(obj); // TypeError: Converting circular structure to JSON
// structuredClone 优雅处理
const cloned = structuredClone(obj);
console.log(cloned.self === cloned); // true
console.log(cloned.children[0].parent === cloned); // true
console.log(cloned !== obj); // true 算法内部维护了一个 (source, clone) 的映射表。当遍历到已经克隆过的对象时,直接返回映射表中的克隆体,从而:
- 避免无限递归
- 保持引用拓扑结构的一致性
3. 底层原理
JSON 方案底层原理
JSON.stringify(ECMAScript JSON 序列化算法)做的事:
- 遍历对象可序列化属性
- 处理 toJSON(如 Date 会先转字符串)
- 生成 JSON 文本(UTF-16 JS 字符串)
JSON.parse 做的事:
- 词法/语法解析 JSON 文本
- 构建全新 JS 对象树
- 可选 reviver 二次转换
本质:文本协议往返。
structuredClone 底层原理
基于 HTML 标准里的 Structured Clone Algorithm(浏览器/运行时实现):
- 从源对象图开始遍历
- 维护“已访问对象表”(解决循环与共享引用)
- 按对象类型走不同克隆分支(Map/Set/Date/TypedArray 等)
- 生成克隆对象图并重建引用关系
- 可选 transfer 列表:对可转移对象“转移所有权”(原对象 detach)
本质:对象图语义复制(不是文本)。
4. 一句话总结
1、JSON.parse(JSON.stringify(obj)):本质是先序列化成 JSON 文本,再解析回对象,是“文本中转”。
2、structuredClone(obj):本质是按结构化克隆算法直接复制内存对象图,不是 JSON 文本中转。
附:structuredClone () 与其他克隆方法详细对比
与 JSON.parse (JSON.stringify ()) 对比
在 JavaScript 中,JSON.parse(JSON.stringify()) 是一种常用的实现深拷贝的方法,但它与 structuredClone() 存在诸多差异。
在支持的数据类型方面,JSON.parse(JSON.stringify()) 仅支持基本数据类型(如数字、字符串、布尔值)、普通对象和数组 。对于 Date 对象,它会将其转换为字符串,在反序列化后不再是 Date 对象,而是普通字符串;Map、Set、RegExp 对象会被转换为空对象;函数、undefined、symbol、Infinity、NaN 以及循环引用等都无法正确处理 。而 structuredClone() 支持的数据类型更为广泛,除了基本数据类型和普通对象、数组外,还支持 Date、RegExp、Map、Set、ArrayBuffer、TypedArrays、Blob、File、ImageData、MessagePort 等,并且能正确处理循环引用 。例如:
// JSON.parse(JSON.stringify()) 处理不支持的类型
const original1 = {
date: new Date(),
map: new Map([['key1', 'value1']]),
func: function() { return 'function' }
};
const jsonClone1 = JSON.parse(JSON.stringify(original1));
console.log(jsonClone1.date instanceof Date); // false,Date 被转为字符串
console.log(jsonClone1.map instanceof Map); // false,Map 被转为空对象
console.log(jsonClone1.func); // undefined,函数丢失
// structuredClone() 处理支持的类型
const original2 = {
date: new Date(),
map: new Map([['key1', 'value1']]),
arrayBuffer: new Uint8Array([1, 2, 3]).buffer
};
const structuredClone2 = structuredClone(original2);
console.log(structuredClone2.date instanceof Date); // true
console.log(structuredClone2.map instanceof Map); // true
console.log(structuredClone2.arrayBuffer instanceof ArrayBuffer); // true在循环引用处理上,JSON.parse(JSON.stringify()) 无法处理对象中的循环引用,当对象存在循环引用时,调用 JSON.stringify() 会抛出 TypeError: Converting circular structure to JSON 错误 ,导致克隆失败。而 structuredClone() 可以正确处理循环引用,确保克隆后的对象包含正确的循环引用结构,不会陷入无限循环 。比如:
// JSON.parse(JSON.stringify()) 处理循环引用
let obj1 = {};
let obj2 = { ref: obj1 };
obj1.ref = obj2;
try {
const jsonClone = JSON.parse(JSON.stringify(obj1));
} catch (error) {
console.error('JSON.parse(JSON.stringify())循环引用错误:', error); // 抛出错误
}
// structuredClone() 处理循环引用
let obj3 = {};
let obj4 = { ref: obj3 };
obj3.ref = obj4;
const structuredClone3 = structuredClone(obj3);
console.log(structuredClone3.ref === structuredClone3.ref.ref); // true,循环引用处理正确性能方面,JSON.parse(JSON.stringify()) 在处理简单的、JSON 兼容的数据结构时性能尚可,但在处理复杂对象或包含大量非 JSON 兼容类型的数据时,由于需要进行序列化和反序列化操作,效率较低 。structuredClone() 是为深度克隆设计的原生方法,内部针对复杂场景进行了优化,通常在处理复杂对象时性能更优 。通过以下测试可以明显看出两者的性能差异:
// 性能测试
const largeArray = new Array(10000).fill({ key: 'value' });
console.time('JSON.parse(JSON.stringify())');
const jsonClone = JSON.parse(JSON.stringify(largeArray));
console.timeEnd('JSON.parse(JSON.stringify())');
console.time('structuredClone');
const structuredClone4 = structuredClone(largeArray);
console.timeEnd('structuredClone');在浏览器兼容性上,JSON.parse(JSON.stringify()) 在现代浏览器和较旧的浏览器中都有广泛支持 。而 structuredClone() 是一种较新的 API,在某些较旧的浏览器中不被支持 ,使用时需要考虑兼容性问题,可以通过检测 typeof structuredClone === 'function' 来判断浏览器是否支持该方法,不支持时可采用其他替代方案。
到此这篇关于JS原生深拷贝的终极方案structuredClone用法代码示例的文章就介绍到这了,更多相关JS原生深拷贝structuredClone内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
