ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

es-toolkit compat 的 cloneWith:用 customizer 定制浅拷贝逻辑的完整指南

2026/9/15 19:43:18 拓冰建站 浏览量
es-toolkit compat 的 cloneWith:用 customizer 定制浅拷贝逻辑的完整指南 es-toolkit compat 的 cloneWith用 customizer 定制浅拷贝逻辑的完整指南【免费下载链接】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本文围绕 es-toolkit 的 Lodash 兼容层compat中的cloneWith函数展开讲解如何通过自定义customizer回调来定制浅拷贝行为涵盖完整参数说明、回退语义、源码级实现原理与性能取舍。读完本文你将掌握cloneWith的全部用法并能根据场景在clone、cloneWith与cloneDeepWith之间做出正确选择。一、cloneWith 是什么cloneWith是 es-toolkit 在 Lodash 兼容层es-toolkit/compat中提供的一个对象拷贝函数其定位是使用自定义函数customizer创建值的浅拷贝。它与标准clone的区别在于你可以传入一个customizer函数由它决定某个值应该如何被复制从而实现拷贝的同时进行数据变换。官方文档位于 docs/compat/reference/object/cloneWith.md该函数从 src/compat/compat.ts 统一导出与clone、cloneDeep、cloneDeepWith等函数并列共同构成 compat 层的克隆工具族。const cloned cloneWith(value, customizer);二、基本用法与参数说明函数签名cloneWith(value, customizer?)从 src/compat/object/cloneWith.ts 的源码看该函数提供了三组类型重载// 带 customizer返回类型为 RR 限定为 object | string | number | boolean | null cloneWithT, R extends object | string | number | boolean | null(value: T, customizer: CloneWithCustomizerT, R): R; // 带 customizercustomizer 可能返回 undefined此时回退到默认拷贝返回 R | T cloneWithT, R(value: T, customizer: CloneWithCustomizerT, R | undefined): R | T; // 不带 customizer等价于 clone cloneWithT(value: T): T;其中 customizer 的类型定义为type CloneWithCustomizerT, R (value: T, key: number | string | undefined, object: any, stack: any) R;参数表格参数类型必填说明valueT是需要被克隆的值可以是对象、数组、原始值或任意 JS 内置类型customizer(value: any) any否决定复制方式的函数若返回undefined则回退到默认拷贝行为返回值(T)返回经 customizer 处理后的浅拷贝结果。最简单的用法不带 customizer不传customizer时cloneWith的行为与clone完全一致import { cloneWith } from es-toolkit/compat; const obj { a: 1, b: hello }; const cloned cloneWith(obj); // Returns: { a: 1, b: hello }新的对象实例与原对象引用不同对应源码src/compat/object/cloneWith.ts中if (!customizer) { return clone(value); }这一分支测试 src/compat/object/cloneWith.spec.ts 也明确验证了未提供 customizer 时使用 clone的行为。三、用 customizer 定制复制逻辑cloneWith的核心价值在于当默认拷贝方式不满足需求时你可以通过customizer对值进行改写。下面完整覆盖官方文档中的三类典型场景。场景一变换数字值import { cloneWith } from es-toolkit/compat; const obj2 { a: 1, b: 2, c: text }; const cloned2 cloneWith(obj2, value { const obj {}; for (const key in value) { const val value[key]; if (typeof val number) { obj[key] val * 2; } else { obj[key] val; } } return obj; }); // Returns: { a: 2, b: 4, c: text }场景二变换数组元素import { cloneWith } from es-toolkit/compat; const arr [1, 2, 3]; const clonedArr cloneWith(arr, value { return value.map(x x 10); }); // Returns: [11, 12, 13]场景三按类型定制处理Date、字符串import { cloneWith } from es-toolkit/compat; const complex { date: new Date(2023-01-01), number: 42, text: hello, }; const clonedComplex cloneWith(complex, value { const obj {}; for (const key in value) { const val value[key]; if (val instanceof Date) { obj[key] val.toISOString(); } else if (typeof val string) { obj[key] val.toUpperCase(); } else { obj[key] val; } } return obj; }); // Returns: { date: 2023-01-01T00:00:00.000Z, number: 42, text: HELLO }可以看到customizer 是一个整体替换函数它接收整个待拷贝值返回一个全新的替换结果。上述示例之所以要遍历value的键正是因为 customizer 只会被调用一次且作用于顶层值详见第五节源码剖析。四、customizer 返回 undefined 时的回退行为这是cloneWith最重要的一条语义如果 customizer 返回undefined则对该值使用默认拷贝行为。import { cloneWith } from es-toolkit/compat; const obj { a: 1, b: { c: 2 } }; const cloned cloneWith(obj, value { // 对所有值都返回 undefined 全部使用默认拷贝 return undefined; }); // Returns: { a: 1, b: { c: 2 } }与 clone 结果一致这一语义在源码中体现为src/compat/object/cloneWith.tsexport function cloneWith(value: any, customizer?: any): any { if (!customizer) { return clone(value); } const result customizer(value); if (result ! undefined) { return result; } return clone(value); }测试文件对这条回退路径做了大量验证src/compat/object/cloneWith.spec.ts当 customizer 返回undefined时普通对象会被克隆为新实例actual与object值相等但引用不同数组同样被克隆为新实例但拷贝是浅拷贝actual.a与object.a仍指向同一个嵌套引用toBe为真见 src/compat/object/cloneWith.spec.ts。需要特别注意的是undefined是唯一的回退信号而null不是。测试 src/compat/object/cloneWith.spec.ts 验证了 customizer 返回null时结果就是null本身。这是与直觉容易混淆的边界请务必记住。五、源码实现剖析customizer 只作用于顶层值从实现层面看cloneWith非常轻量——它本身并不递归遍历对象而是把全部工作委托给clone。整个流程只有三步若无customizer直接返回clone(value)调用customizer(value)得到结果结果不为undefined则返回该结果否则回退到clone(value)。由此可以推导出一个重要实现事实customizer只会被调用一次且参数只有顶层值本身。测试 src/compat/object/cloneWith.spec.ts 验证了调用 customizer 时只传入了一个参数[object]src/compat/object/cloneWith.spec.ts 与 src/compat/object/cloneWith.spec.ts 进一步确认了customizer 仅定制顶层值const obj { num: 42, str: test, }; const result cloneWith(obj, value { if (typeof value number) { return value * 2; } }); // result.num 仍为 42因为 customizer 只拿到整个 obj对象而不是其中的 num换句话说类型签名中声明的key、object、stack参数在当前 compat 实现中并未实际传入调用——它们是为与 Lodash 签名保持兼容而预留的。如果你需要对每个嵌套值都执行定制逻辑应当使用cloneDeepWith见第七节。六、回退路径的底层clone 的浅拷贝能力边界当 customizer 返回undefined时cloneWith委托给clone。理解clone的能力边界也就理解了cloneWith的默认行为。从 src/compat/object/clone.ts 的实现看原始值isPrimitive判断原样返回不做拷贝数组通过Array.from创建新数组对正则匹配结果数组带index/input属性的数组会额外复制这两个属性类型化数组TypedArray基于同一buffer创建新视图共享底层内存ArrayBuffer / DataView创建新 buffer 并复制字节内容装箱原始值Boolean/Number/String 对象用valueOf()重建并复制自有属性Datenew Date(Number(obj))重建RegExp保留source、flags并复制lastIndexSymbol 对象通过Symbol.prototype.valueOf重建Map / Set创建新实例并逐个放入原键值arguments 对象复制自有属性、length与Symbol.iterator普通对象复制原型copyPrototype、自有属性copyOwnProperties与可枚举的 Symbol 属性copySymbolProperties。对应地isCloneableObject白名单之外的值函数、async 函数、generator 函数、Proxy 构造函数、DOM 元素、各类 Error 对象等不会被克隆clone对它们返回{}——这在测试 src/compat/object/cloneWith.spec.ts 中被称为uncloneable objects并逐类验证。测试还覆盖了大量边界null 原型对象、被篡改的constructor、遮蔽Object.prototype属性的对象、可枚举/不可枚举的 Symbol 属性、Foo.prototype克隆等见 src/compat/object/cloneWith.spec.ts。这些测试大多沿用 Lodash 官方测试用例体现了 compat 层行为对齐 lodash的承诺。七、性能提示与最佳实践官方文档在开头给出了明确的性能警告建议实现自定义逻辑而不是使用cloneWith该函数因复杂的 customizer 处理而相对较慢。这是 compat 层为兼容 Lodash 行为所做的权衡。因此最佳实践是普通浅拷贝直接使用clonees-toolkit/compat或标准入口均可避免为cloneWith额外传入一个恒返回undefined的 customizer深度拷贝使用cloneDeep需要逐层定制的深度拷贝使用cloneDeepWith它会在遍历每个节点时调用 customizersrc/compat/object/cloneDeepWith.ts只有当你的定制逻辑只针对顶层值、且无法用 clone 加后续变换简单表达时才考虑cloneWith。值得注意的是因为 customizer 只处理顶层值许多看起来需要 cloneWith的场景其实可以退化为clone后手动变换或直接用cloneDeepWith这也是文档建议使用 clone 并直接实现自定义逻辑的原因。八、克隆函数族速查函数拷贝深度定制能力适用场景clone浅拷贝无大多数浅拷贝需求cloneWith浅拷贝仅顶层值顶层值需要特殊处理、且要保留浅拷贝语义cloneDeep深拷贝无深拷贝、隔离嵌套引用cloneDeepWith深拷贝逐层调用深拷贝中需要对每个节点定制如日期序列化、数字放大四者均从 src/compat/compat.ts 导出可通过import { clone, cloneWith, cloneDeep, cloneDeepWith } from es-toolkit/compat;使用。九、总结cloneWith是 es-toolkit compat 层中一个小而巧的兼容函数它通过一次顶层 customizer 调用 回退到clone的两段式实现src/compat/object/cloneWith.ts完整复刻了 Lodash 的_.cloneWith语义——undefined返回即回退null则作为有效结果返回。理解其customizer 仅作用于顶层值的实现事实与浅拷贝 共享嵌套引用的语义边界就能在实际项目中正确使用它并在需要深度定制时无缝切换到cloneDeepWith。若需要进一步了解 compat 层的整体设计与其他克隆函数可继续阅读 docs/compat/intro.md 与 cloneDeepWith 文档若存在及对应源码。【免费下载链接】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),仅供参考