深拷贝的核心目标是隔离打印上下文,确保导出样式、分页等不影响原始数据;现代环境优先用structuredClone(),旧环境需自定义实现并处理循环引用、Map/Set/Date等,且按配置层深拷贝、明细层选择性优化。

在复杂报表导出工具中,深拷贝的核心目标不是“复制数据”,而是“隔离打印上下文”——确保导出时的样式、分页、折叠状态、临时计算字段等不影响原始数据,也不被其他导出任务干扰。对多维数据对象(如含嵌套数组、Map/Set、Date、RegExp、函数、循环引用的报表配置+数据集),简单 JSON.parse(JSON.stringify()) 会丢弃函数、undefined、Date 对象、正则、原型链,且无法处理循环引用。
优先用结构化克隆(现代环境首选)
Chrome 98+、Firefox 94+、Edge 98+ 支持 structuredClone(),它原生支持 Map、Set、Date、RegExp、ArrayBuffer、TypedArray、Error、Blob、URL 等,并自动处理循环引用,且不执行 getter/setter,适合报表配置对象的干净快照:
- 直接调用:
const printCopy = structuredClone(reportData); - 适用于导出前的“只读快照”:样式配置、分组维度、筛选条件、列宽设置等都完整保留
- 注意:不复制函数和 DOM 节点(报表工具中函数通常为纯逻辑,应单独注入;DOM 节点本就不该进数据层)
兼容旧环境需自定义深拷贝函数
当需支持 IE 或老版 Electron 时,避免第三方库(如 Lodash)可引入轻量健壮实现。关键点是识别并跳过不可序列化字段,同时显式处理常见类型:
- 递归遍历时用 WeakMap 缓存已拷贝对象,解决循环引用
- Date → 新 Date 实例;RegExp →
new RegExp(source.pattern, source.flags) - 忽略函数、undefined、Symbol 键(报表数据中这些通常属行为逻辑,非状态)
- 对 Map/Set 单独构造:
new Map(original.map(([k, v]) => [k, deepClone(v)])) - 对报表中常见的“带元信息的数组”(如
[{value: 123, _isTotal: true}]),保持其结构和自有属性
按需浅层 + 深层混合拷贝更高效
报表数据常分两层:顶层配置(小而关键)+ 底层明细数据(大但结构扁平)。全量深拷贝开销大。推荐策略:
立即学习“Java免费学习笔记(深入)”;
- 配置层(columns、filters、pagination、printOptions)用深拷贝(含嵌套对象)
- 明细数据层(rows 数组)若仅为纯 JSON 数据(无方法/原型),可用
JSON.parse(JSON.stringify(rows))加速;若有 Date 等,则对 rows 中每个 item 单独深拷贝 - 导出前统一挂载打印专用字段(如
__pageBreakAfter: true),这些字段写入副本,不污染源数据
导出流程中绑定副本生命周期
深拷贝不是终点,而是隔离起点。建议在导出模块内封装副本管理:
- 导出任务启动时生成副本,并用 Symbol 作为唯一标识关联到当前 task ID
- 所有打印样式计算、分页逻辑、合计行插入,均基于该副本操作
- 导出完成或取消后,显式释放大对象引用(尤其 rows 数组),避免内存滞留
- 不将副本暴露给全局或 Vue/React 响应式系统(防止意外触发重渲染)



















