es-toolkit cloneDeep 深度克隆完全指南从基本用法到循环引用与源码级原理【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkitcloneDeep是 es-toolkit 提供的高性能深拷贝工具函数位于es-toolkit/object模块用于将对象、数组乃至Map、Set、Date、RegExp等内置类型连同全部嵌套结构完整复制为彼此独立的新值同时安全处理循环引用。读完本文你将掌握cloneDeep的完整 API、各类数据类型的复制行为、getter 属性与类实例等边界情况并通过源码与测试用例理解其底层实现原理从而在实际项目中正确选用深浅拷贝方案。一、cloneDeep 是什么cloneDeep会为给定值创建一份深度拷贝不仅复制最外层对象还会递归复制所有嵌套的对象与数组。这意味着复制结果与原值在内存中完全隔离修改任意一方都不会影响另一方。其函数签名如下const deepCloned cloneDeep(obj);当我们需要完全独立的对象副本时例如状态快照、表单初始值备份、跨模块传值等场景cloneDeep就是最直接的选择。二、基本使用对象、数组与原始值从 cloneDeep 官方文档 出发最典型的使用方式如下import { cloneDeep } from es-toolkit/object; // 原始值primitive直接原样返回 const num 29; const clonedNum cloneDeep(num); console.log(clonedNum); // 29 console.log(clonedNum num); // true // 嵌套对象的深拷贝 const obj { a: { b: { c: deep } }, d: [1, 2, { e: nested }] }; const clonedObj cloneDeep(obj); console.log(clonedObj); // { a: { b: { c: deep } }, d: [1, 2, { e: nested }] } console.log(clonedObj obj); // false console.log(clonedObj.a obj.a); // false嵌套对象也被复制 console.log(clonedObj.d obj.d); // false嵌套数组也被复制 console.log(clonedObj.d[2] obj.d[2]); // false数组内的对象也被复制 // 修改原值不会影响副本 const original { a: { count: 1 } }; const copied cloneDeep(original); original.a.count 2; console.log(copied.a.count); // 1保持不变需要特别强调的是深拷贝与浅拷贝如{ ...obj }或structuredClone之前的常见做法有本质区别浅拷贝只复制最外层引用嵌套对象仍然共享而cloneDeep保证从根到叶的每一层都是全新副本。上面示例中clonedObj.a obj.a为false正是这一点的直接体现。参数与返回值参数obj类型T要深度复制的值可以是对象、数组、原始值等任意类型。返回值类型T给定值的深拷贝。三、内置类型支持Map、Set、Date、RegExp 等cloneDeep不仅支持普通对象与数组还支持多种 JavaScript 内置类型。文档中特别演示了Map和Set的深拷贝// Map 与 Set 的深拷贝 const map new Map([[key, { nested: value }]]); const clonedMap cloneDeep(map); console.log(clonedMap ! map); // true console.log(clonedMap.get(key) ! map.get(key)); // true嵌套对象也被复制从 源码实现 可以看到cloneDeepWithImpl按类型逐一分支处理类型复制方式源码位置Array新建同长度数组并递归复制每个元素若为RegExp.exec()返回的数组还会保留index与input属性cloneDeepWith.tsDatenew Date(value.getTime())按时间戳重建cloneDeepWith.tsRegExp以source与flags重建并保留lastIndexcloneDeepWith.tsMap新建Map键保持原样值递归复制cloneDeepWith.tsSet新建Set成员递归复制cloneDeepWith.tsBuffersubarray()复制cloneDeepWith.tsTypedArrayUint8Array等按原型构造器新建同长度数组逐元素复制cloneDeepWith.tsArrayBuffer/SharedArrayBufferslice(0)复制底层二进制数据cloneDeepWith.tsDataView复制底层 buffer 并保留byteOffset、byteLengthcloneDeepWith.tsFile/Blob按相同type新建对象含旧版 Node 兼容判断cloneDeepWith.tsError及其子类基于structuredClone并显式重建message、name、stack、cause、constructorcloneDeepWith.tsBoolean/Number/String包装对象按valueOf()重建包装对象并复制属性cloneDeepWith.ts普通对象 /arguments对象 / 类实例Object.create(原型)后递归复制所有自有可枚举属性含 Symbol 键cloneDeepWith.ts这些行为都有对应的测试用例验证例如 cloneDeep.spec.ts 中对Date、RegExp、Set、Map、各类 TypedArray、ArrayBuffer、DataView、Buffer、File、Blob、Error系列以及包装对象的断言。类实例的复制对类实例cloneDeep会保留其原型链——复制结果仍然是同一个类的实例方法可以正常调用class CustomClass { value: number; constructor(value: number) { this.value value; } getValue() { return this.value; } } const instance new CustomClass(123); const clonedInstance cloneDeep(instance); console.log(clonedInstance).toBeInstanceOf(CustomClass); // true console.log(clonedInstance.getValue()); // 123这一点由 cloneDeep.spec.ts 中的 should clone class instance 用例确认。不过需要注意源码注释与测试表明类实例中的JS 私有字段#b不会被复制因为私有字段不参与属性枚举而 TypeScript 的private字段编译后为普通属性会被复制函数类型属性如d: () number会保持同一引用。四、循环引用安全处理且保持结构深拷贝最容易踩的坑是循环引用——对象直接或间接引用自身若实现不当会无限递归导致栈溢出。cloneDeep通过内部Map栈记录原值 → 副本的映射遇到已复制的值直接返回对应副本从而既避免死循环又保证循环结构在副本中得以保留。// 循环引用也能安全处理 const circular: any { name: test }; circular.self circular; const clonedCircular cloneDeep(circular); console.log(clonedCircular ! circular); // true console.log(clonedCircular.self clonedCircular); // true循环引用被保留从源码看stack在每次进入可复制类型分支前先做stack.has(valueToClone)检查并stack.set(...)登记见 cloneDeepWith.ts数组、Map、Set、TypedArray 等分支都会提前将占位副本写入栈中再递归复制子项这正是循环引用能被正确还原的关键。五、getter 与只读属性按值落地对于用 getter 定义的只读属性cloneDeep不会试图复制 getter 本身而是读取 getter 的返回值将其作为普通属性存入副本const source { get computedValue() { return 42; }, normalValue: hello, }; const cloned cloneDeep(source); console.log(cloned); // { computedValue: 42, normalValue: hello }这与 copyProperties 的实现一致它枚举源对象的所有自有键含 Symbol 键与对应属性描述符仅当目标上不存在该属性或该属性可写writable时才写入副本getter 的求值结果自然落为普通值。测试用例 should clone read-only propertiescloneDeep.spec.ts也验证了这一行为。六、不可克隆对象按原样返回并非所有对象都能被克隆。源码最后的兜底分支通过isCloneableObject基于Object.prototype.toString的 tag 判断见 cloneDeepWith.ts决定是否走克隆流程无法识别的对象如 DOM 元素会被原样返回// 在浏览器环境中 const element document.createElement(div); element.textContent Hello, World!; const clonedElement cloneDeep(element); console.log(clonedElement element); // trueHTMLElement 不可克隆原样返回该行为由 cloneDeep.dom.spec.ts 在 happy-dom 环境下验证测试名为 should not clone uncloneable objects likeHTMLElements。七、源码结构cloneDeep 如何实现cloneDeep的实现非常简洁——它是更通用函数cloneDeepWith的特例// src/object/cloneDeep.ts import { cloneDeepWithImpl } from ./cloneDeepWith.ts; export function cloneDeepT(obj: T): T { return cloneDeepWithImpl(obj, undefined, obj, new Map(), undefined); }核心逻辑全部位于 cloneDeepWithImpl其处理流程可概括为若提供了自定义复制函数cloneValue先调用它返回非undefined则直接采用该结果原始值isPrimitive原样返回检查stack是否已有该值的副本循环引用处理按第三节表格中的类型顺序逐一分支克隆普通对象走Object.create(原型)copyProperties递归复制其余无法识别的对象原样返回。这也解释了为什么 cloneDeep 的入口实现只有一个函数调用深度复制的全部能力类型分发、循环引用栈、getter 处理都收敛在cloneDeepWithImpl中cloneDeep只负责以无自定义函数的方式调用它。八、与相关函数的关系cloneDeepWith(obj, cloneValue)cloneDeep的定制化版本允许传入自定义复制函数在复制过程中对特定值做特殊处理例如对Date统一偏移时间、对数值做变换等。二者共享同一实现cloneDeepWithImpl可参考 cloneDeepWith 文档 与 cloneDeepWith.ts。clone浅拷贝es-toolkit 同时提供浅拷贝的clone只复制最外层结构。选择原则很简单只需独立顶层引用用clone需要完全隔离的嵌套数据用cloneDeep。compat 模块为 lodash 兼容场景es-toolkit 还在es-toolkit/compat下提供了对齐 lodash 语义的 cloneDeep 实现内部同样委托给cloneDeepWith方便从 lodash 平滑迁移。九、在项目中使用在 es-toolkit 项目中cloneDeep可从子路径导入以获得最优的 tree-shaking 效果import { cloneDeep } from es-toolkit/object;也可从主入口导入会随包整体引入import { cloneDeep } from es-toolkit;。函数声明与导出位置见 src/object/cloneDeep.ts 与 src/object/index.ts。建议在以下场景优先选用需要保存状态快照后续修改不能回写原对象将可变数据安全传入不受控的外部代码插件、Web Worker 消息等需要复制包含Map、Set、Date、RegExp、TypedArray 等复杂类型的配置对象。十、小结es-toolkit 的cloneDeep提供了覆盖完整 JavaScript 类型体系的深拷贝能力嵌套对象与数组、Map/Set、Date/RegExp、二进制类型、Error与包装对象、类实例均能得到正确的独立副本循环引用通过内部栈安全处理且结构保持不变getter 属性以值形式落地不可克隆对象如 DOM 元素则原样返回。借助 cloneDeep.ts 与 cloneDeepWith.ts 的源码以及 cloneDeep.spec.ts 与 cloneDeep.dom.spec.ts 的测试覆盖你可以放心在需要深度隔离数据的任何场景使用它。【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考