ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

es-toolkit assignInWith:带自定义合并逻辑的继承属性对象合并方案

es-toolkit assignInWith:带自定义合并逻辑的继承属性对象合并方案 es-toolkit assignInWith带自定义合并逻辑的继承属性对象合并方案【免费下载链接】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/compat 中assignInWith别名extendWith的完整用法与底层实现。它用于把多个源对象的自有属性与继承属性合并进目标对象并允许通过customizer回调逐属性定制赋值结果适用于需要仅覆盖 undefined 值合并数组对继承属性做加工等 Lodash 兼容场景。读完本文你将掌握assignInWith的参数约定、customizer 的返回值语义、与assignIn/Object.assign的差异以及它在 src/compat/object/assignInWith.ts 中的真实实现原理。一、函数定位带自定义逻辑的 assignInassignInWith是 Lodash 兼容 APIes-toolkit/compat中的对象工具函数其核心行为是从多个源对象向目标对象赋值所有属性包括继承自原型链的属性并使用customizer函数决定每个属性的最终取值。官方签名如下const result assignInWith(target, ...sources, customizer);它与不带customizer的 assignIn 的区别在于assignIn只是照搬源对象的值而assignInWith允许你在赋值前对每个属性执行自定义逻辑。从调用形态看customizer作为最后一个参数传入函数会先判断最后一个参数是否为函数是则视为customizer否则按普通源对象处理见 实现源码。二、参数与返回值参数类型说明targetany属性被复制到的目标对象会被原地修改并返回...sourcesany[]提供属性的源对象自有属性与继承属性都会被复制customizerfunction形如(objValue, srcValue, key, object, source) any的函数决定每个属性要赋的值返回undefined时回退到默认赋值行为返回值any返回修改后的目标对象本身原地修改非浅拷贝新对象。其中customizer的五个参数分别对应目标对象的当前值objValue、源对象的值srcValue、属性名key、目标对象整体object、源对象整体source。实际调用时并不要求全部使用文档示例多只用到前两个或前三个参数。三、核心用法示例3.1 仅在值为 undefined 时赋值import { assignInWith } from es-toolkit/compat; const target { a: 1, b: undefined }; const source { b: 2, c: 3 }; const result assignInWith(target, source, (objValue, srcValue) { return objValue undefined ? srcValue : objValue; }); // Result: { a: 1, b: 2, c: 3 }这是最常见的缺省值填充模式a已有值1保持不变b原为undefined被填充为2c是目标对象没有的属性objValue为undefined因而取源值3。3.2 用 customizer 合并数组值const target2 { numbers: [1, 2] }; const source2 { numbers: [3, 4], name: test }; assignInWith(target2, source2, (objValue, srcValue) { if (Array.isArray(objValue) Array.isArray(srcValue)) { return objValue.concat(srcValue); } return srcValue; }); // Result: { numbers: [1, 2, 3, 4], name: test }这里numbers双方都是数组customizer 将其拼接name不是数组回退为直接取srcValue。这展示了 customizer 做类型分派type dispatch的典型写法。3.3 处理继承属性function Parent() {} Parent.prototype.inherited value; const child Object.create(Parent.prototype); child.own ownValue; const target3 { existing: data }; assignInWith(target3, child, (objValue, srcValue, key) { if (objValue undefined) { return processed_${srcValue}; } return objValue; }); // Result: { existing: data, own: processed_ownValue, inherited: processed_value }关键点在于inherited是挂在Parent.prototype上的属性并非child的自有属性。assignInWith依然会把它枚举出来并参与赋值这是它与Object.assign、assign系列只处理自有属性函数最本质的差异。从 keysIn 源码 可以看到它内部通过for...in遍历键名——for...in天然会包含原型链上的可枚举属性这正是包含继承属性这一行为的实现来源。四、customizer 的返回值语义文档中有一个必须牢记的约定如果customizer返回undefined则使用默认赋值行为即直接取源对象的值。这与实现完全对应。在 assignInWithImpl 中const newValue getValueToAssign?.(objValue, srcValue, key, object, source) ?? srcValue; if (!(key in object) || !eq(objValue, newValue)) { object[key] newValue; }customizer的返回值通过??空值合并运算符处理只有返回undefined或未提供 customizer时才回退到srcValue。注意null不会被回退——null ?? x会返回null说明customizer返回null时会被当作有效结果直接赋值。在 assignInWith.spec.ts 中有对应测试当 customizer 为noop恒返回undefined时结果退化为默认赋值行为与assignIn等价。五、源码级原理它到底做了什么assignInWith的整体实现分为三层assignInWith.ts参数解析取出最后一个参数若为函数则作为getValueToAssign并从源列表移除否则置为undefined等价于无 customizer。逐个源对象合并对每个源对象调用assignInWithImpl依次合并后面的源对象属性会覆盖前面源对象已赋的值。单对象合并用keysIn(source)取键逐个属性执行取 customizer 结果 → 判断是否需要写入 → 赋值。其中有两个值得注意的细节相等属性跳过写入!(key in object) || !eq(objValue, newValue)意味着当目标对象已有该键且新旧值相等时跳过赋值。这里的eq来自 src/compat/util/eq.ts可进一步查看其实现这是一种避免无意义写入的优化。类型重载源码为 14 个源对象分别声明了T U、T U V等精确的交叉类型重载assignInWith.ts#L25-L116并提供一个(object, ...otherArgs) R的通用重载兜底保证 TypeScript 下多源对象合并的返回类型推断准确。keysIn本身也是一个值得单独了解的工具函数src/compat/object/keysIn.ts 会针对null/undefined返回空数组对类数组对象按数组索引展开对原型对象isPrototype剔除constructor键其余情况通过for...in收集包括继承链在内的全部字符串键。六、别名 extendWithassignInWith在 Lodash 中有一个历史别名extendWith。es-toolkit 也保留了这一别名见 src/compat/object/extendWith.tsexport { assignInWith as extendWith } from ./assignInWith.ts;两者是同一个函数的引用extendWith.spec.ts 中通过expect(extendWith).toBe(assignInWith)直接验证了别名等价性。extendWith同样从es-toolkit/compat导出见 src/compat/compat.ts 与 src/browser.ts。从旧代码迁移时两种写法都可直接使用。七、与相关函数的对比与选型建议函数是否含继承属性是否支持 customizer适用场景Object.assign原生否否简单自有属性合并assigncompat否否自有属性合并assignWithcompat否是自有属性 自定义逻辑assignIncompat是否含原型链属性合并assignInWith/extendWith是是含原型链属性 自定义逻辑八、性能提示与官方建议文档开头有一段醒目的警告assignInWith.md由于涉及继承属性处理与 customizer 回调assignInWith运行速度较慢且实现复杂。建议改用Object.assign并直接实现自定义逻辑。这是合理的工程建议大多数业务场景处理的都是普通自有属性Object.assign由引擎原生实现性能更好且无原型链遍历开销继承属性的遍历与逐属性回调本身就比简单赋值昂贵。es-toolkit 也提供了对应的性能基准测试 benchmarks/performance/assignInWith.bench.ts其中 es-toolkit 与 lodash 使用相同的(objValue, srcValue) objValue undefined ? srcValue : objValue回调对比测试可供自行运行验证。只有当确实需要含继承属性 逐属性自定义逻辑如处理老式基于原型链的数据模型且为兼容 Lodash 存量代码时才优先考虑assignInWith。九、安装与导入方式assignInWith属于es-toolkit/compat兼容层与严格 APIes-toolkit区分详见 compat 介绍。安装依赖后有以下导入方式// 从 compat 入口导入 import { assignInWith, extendWith } from es-toolkit/compat; // 按需单函数导入无 tree-shaking 环境下可减小加载体积 // 适用于 CommonJS require、React Native 或 Node.js 直跑场景 import assignInWith from es-toolkit/compat/assignInWith; // 或 const assignInWith require(es-toolkit/compat/assignInWith);如 compat 介绍 所述es-toolkit/compat旨在 1:1 镜像 Lodash 的接口与行为便于存量 Lodash 代码无改动切换入口后再逐步迁移到严格 API。若项目未使用 Lodash官方建议直接使用es-toolkit的现代严格 API。十、小结assignInWith是 es-toolkit/compat 中能力最完整的对象合并工具之一它以keysInfor...in实现包含继承属性以customizer ?? srcValue实现返回 undefined 即回退默认赋值以重载声明保证多源对象的类型安全并保留extendWith别名。理解其参数语义与 源码实现 后你既能在 Lodash 迁移场景中正确使用它也能在性能敏感的场景中判断何时该改用Object.assign 自定义逻辑。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进