在 JavaScript 开发中,对象的深拷贝一直是一个经典痛点。从 JSON.parse(JSON.stringify()) 的 hack,到 Lodash 的 _.cloneDeep(),开发者们一直在找一个既可靠又高效的深拷贝。2022 年,structuredClone 作为全局 API 正式进入所有主流浏览器和 Node.js,终结了这场漫长的探索。
1. 基本用法
javascript
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 选项,这是它相比其他深拷贝方案的独特能力:
javascript
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); // 104857600
Transfer 的本质是所有权转移(ownership transfer),不是复制。底层只是修改了内存区域的归属指针,时间复杂度 O(1)。
为什么要这样设计?
核心原因是性能。如果要把 100MB 的视频帧数据发给 Web Worker 处理:
javascript
// 方式 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,绑定了渲染树,跨上下文无意义
- 保留对象图结构:可保留"同一引用关系"和循环结构
关键:原型链丢失
javascript
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); // true
structuredClone 克隆的是数据,不是行为。
对比 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 方案的核心优势之一:
javascript
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 文本中转。