JavaScript structuredClone:深拷贝能保留什么,transfer 又会拿走什么
把对象转成 JSON 再解析,常被当成通用深拷贝,但日期会变成字符串,循环引用会失败。有些状态需要保留类型和内部引用关系,适合考虑 structuredClone。本文示例在 Node.js 24.19.0 执行;Node 自十七版本加入该全局函数。浏览器使用时仍应按目标环境确认支持情况。
AI生成概念图:两个独立图形组保留相同的内部连接关系。仅作概念说明,不代表实际界面或实测数据。
用类型与引用关系验收副本
下面的对象包含日期、Map 中的嵌套值,以及指向自身的属性。验收不只比较输出长得是否一样,还检查日期与 Map 的类型、副本是否指向自己、修改副本会不会影响原对象。整段保存为扩展名 cjs 的文件,用 Node 执行即可;成功时会打印三组结果。
const assert = require("node:assert/strict");
const source = {
when: new Date("2026-01-02T03:04:05Z"),
scores: new Map([["team", { value: 2 }]]),
};
source.self = source;
const copy = structuredClone(source);
assert.notEqual(copy, source);
assert.equal(copy.self, copy);
assert.ok(copy.when instanceof Date);
assert.ok(copy.scores instanceof Map);
copy.scores.get("team").value = 9;
assert.equal(source.scores.get("team").value, 2);
assert.equal(copy.when.getTime(), source.when.getTime());
console.log("clone: Date, Map, cycle and isolation verified");
const jsonCopy = JSON.parse(JSON.stringify({ when: source.when }));
assert.equal(typeof jsonCopy.when, "string");
assert.throws(() => JSON.stringify(source), TypeError);
assert.throws(() => structuredClone({ run() {} }),
{ name: "DataCloneError" });
console.log("JSON date:", jsonCopy.when);
const buffer = new ArrayBuffer(4);
new Uint8Array(buffer)[0] = 42;
const moved = structuredClone(buffer, { transfer: [buffer] });
assert.equal(buffer.byteLength, 0);
assert.equal(new Uint8Array(moved)[0], 42);
console.log("transfer lengths:", buffer.byteLength, moved.byteLength);第一组断言通过,表示副本是新对象,内部循环仍指回副本,而且把副本分数改成九后,原分数仍为二。克隆需要保持的是图中的关系,而非把所有指针照原地址抄过去。如果多个属性原本共享同一对象,也应把这种共享关系加入应用自己的测试。
JSON 往返与克隆解决不同需求
中间一段故意只对日期做 JSON 往返,结果是带时区的字符串,已经不再是 Date 实例。随后再序列化含循环的原对象,断言它抛出类型错误。接口传输本来就可能要求字符串,这时 JSON 是合理的数据格式;问题在于把格式转换产生的类型变化误认成完整复制。
同样,structuredClone 也不是任意对象复制器。示例中的函数属性会触发 DataCloneError,因此把含回调、界面节点或运行时资源的应用状态整包丢进去,可能失败。先划出可序列化的数据部分,比遇到异常后不断删字段更容易维护。失败应传回调用者,不能默默退回浅拷贝。
转移后,原来的缓冲区不能继续使用
最后一段给出一个四字节 ArrayBuffer,写入数值四十二,再把它放入 transfer 列表。结果原缓冲区长度为零,新缓冲区长度为四且数据保留。这里是转移可转移对象的资源所有权,并非同时留给两方各一份。后续仍要读取原缓冲区的代码必须调整。
这类操作适合明确交接数据的流程,例如把大块数据交给工作线程处理,但不应只为了追求“更快”而随手打开。需要先明确谁负责后续访问、失败时如何恢复,以及哪些对象真的可转移。示例只验证行为,未测量性能,也不据此承诺任何速度提升。
业务对象最好显式定义重建方式
自定义类的原型链、方法和属性描述符等,不能被当作结构化克隆的完整保留合同。若对象有不变量、私有状态或资源句柄,宜先导出普通数据,再由明确的构造流程重建。不要仅凭打印结果像原对象,就认为它还能调用同样的方法。
上线前按实际数据形状准备测试:嵌套可变值、循环、共享引用、不可克隆成员和转移后的原对象。还要估算输入体积与同步克隆可能占用的时间,避免在交互主线程无上限复制。只有类型、关系、隔离和所有权四项都符合预期,才算选对了复制方式。


