纯数据 JSON 更快、二进制快 130 倍:structuredClone 深拷贝选型实测复盘
一句「用 structuredClone 替换 JSON 深拷贝」的推荐在 2025–2026 的不少教程里反复出现理由是「原生、快、不会丢类型」。但把它写进热路径之前很少有人拿真实数据量过。我们这次用 Node 22 在五种数据形状上做了实测结论反直觉——纯 JSON 形状的数据上老旧的JSON.parse(JSON.stringify())其实比 structuredClone 还快 12%~26%真正让后者反超的只有「共享引用」和「TypedArray」。更隐蔽的风险在类型保真JSON 深拷贝会静默把 Date、Map、Set 变成别的类型把函数直接丢掉这比慢更致命。背景为什么深拷贝总被顺手写错深拷贝是前端和 Node 里的高频操作表单快照、Redux/Zustand 状态拷贝、Worker 边界传数据、接口响应防污染。最常见的两种写法只有一行「JSON 大法」const copy JSON.parse(JSON.stringify(src))「现代写法」const copy structuredClone(src)社区共识倾向于后者它是语言原生 API不用引库还能保留 Map、Set、Date。但「快」这件事几乎没人用真实数据验证过。本文的目标不是站队而是把五个典型数据形状丢进基准测试看数字到底怎么说——以及除了快慢还有哪些坑会让「能跑」和「跑对」之间隔着一条深沟。解剖两条 Clone 路径到底差在哪两种写法的底层路径完全不同这决定了它们在不同形状上的表现。图1JSON 路线要先「对象 → 字符串 → 对象」两次遍历且每个数组下标都要做整数到字符串的序列化structuredClone 走内存级的结构化克隆用备忘录表跳过共享引用TypedArray 直接整块内存拷贝。JSON.parse(JSON.stringify())走的是「序列化再反序列化」先把整棵对象树遍历成字符串再把字符串解析回对象。这条路径对「纯 JSON 形状」普通对象、数组、字符串、数字非常友好——V8 给 JSON 单独做了高度优化的字节码路径连数组下标都不必做类型转换以外的额外工作。structuredClone走的是结构化克隆算法递归遍历对象遇到已见过的引用直接写回边back-reference遇到 TypedArray 直接整块内存搬运。代价是每次克隆都要维护一张备忘录表并对原型链做处理。所以对象很小时这份「簿记」开销会盖过收益一旦对象里有共享子结构或二进制数据它立刻反转局势。实证五个形状的实测数据我们在 Node v22.22.2 上对五种形状各跑 200 轮取中位数TypedArray 取 50 轮。复现命令如下function median(a){a.sort((x,y)x-y);const na.length,mn1;return n%2?a[m]:(a[m-1]a[m])/2;} function t(fn,iters){for(let i0;iMath.min(50,iters);i)fn();const s[];for(let i0;iiters;i){const aperformance.now();fn();s.push(performance.now()-a);}return median(s);}实测结果单位 ms倍率 JSON / structuredClone小于 1 表示 JSON 更快数据形状JSON round-tripstructuredClone倍率小扁平对象10 字段0.0020.0020.79嵌套 2000 用户2.022.730.74宽表 5 万键16.819.10.88共享引用 2000 处1.260.711.78Float64Array 20 万51.10.39130.7图2柱状图展示五个形状下两种写法的耗时。纯 JSON 形状前三组JSON 反而快 12%~26%一旦出现共享引用或 TypedArraystructuredClone 反超并拉开巨大差距。读这张表的关键不是「谁更快」而是「在什么形状下更快」纯 JSON 形状普通对象/数组small 和 nested 都是 JSON 胜出差距 12%~26%。V8 的 JSON 快路径确实强结构化克隆的备忘录开销在小对象上不划算。共享引用2000 处指向同一个子对象JSON 把同一棵子树重复序列化 2000 次structuredClone 只克隆一次并写回边反超 1.78×。Redux 规范化状态里「同一个 user 被多个 slice 引用」正是这种场景。TypedArray20 万浮点JSON 要把每个数字格式化成十进制字符串再解析回来structuredClone 直接内存搬运差距拉到 130×——这才是「结构化克隆」真正发光的地方。局限比速度更危险的是「静默变形」速度只是第一层。把{d:new Date(), m:new Map(), s:new Set([1,2,3]), f:()1}分别过一遍两条路径差异才是真正的坑图3同一份数据经两条路径后类型的归宿。JSON 会把 Date 变成字符串、Map/Set 变成普通对象、函数直接消失structuredClone 保住前三者但遇到函数会直接抛错。DateJSON 后变成字符串String再也不是 DatestructuredClone 正确保留。Map/SetJSON 后变成普通对象{}/{}方法全丢structuredClone 保留。FunctionJSON 静默丢弃拷贝后字段直接没了structuredClone 直接抛DOMException——至少「看得见」。「静默变形」比报错更危险JSON 那条路径在测试环境恰好没有这些类型跑得好好的上线遇到真实数据就悄悄把时间、集合类型改了等到对账才发现。所以选型的第一原则不是「谁快」而是「你的数据里有没有非 JSON 类型」——有的话JSON 大法从根上就不该用。结论与下一步把这次实测收成一条可执行的规则默认用structuredClone现代浏览器和 Node 17 已内置零依赖且能保住 Date/Map/Set。纯 JSON 形状且追求极致性能时JSON 大法确实略快但只在「小到亚毫秒」的量级——这点差距通常不值得用正确性去换。永远别用 JSON 大法处理含 Date/Map/Set/函数的数据它不是「慢」是「会变形」。需要拷贝函数或类实例才轮到 lodash.cloneDeep 或手写递归。迁移老代码时先grep一遍JSON.parse(JSON.stringify再逐处判断——它到底是要「序列化」还是只想「拷贝」。实测代码与数据已归档可对照复跑下一步建议把这条规则做成团队的 ESLint 规则或 Code Review 清单从源头拦住「静默变形」。开源地址矩阵门户GitHub - wangzifan396-wzf/WB: nano-tools: 1200 single-file, zero-dependency, local-first web utilities in one portal. Offline and private, nothing leaves your browser. Binary protocol parsers, crypto, dev, audio, visualization, productivity. · GitHub单文件工具聚合器GitHub - wangzifan396-wzf/nano-workbench: Single-file tabbed launcher for the nano-tools matrix - one tab, 382 curated tools (of 1228), instant switching. Zero-dep. Part of nano-tools. · GitHubGitHub 组织主页wangzifan396-wzf (WangZi) · GitHub

相关新闻