ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入解析 es-toolkit 的 extendWith(assignInWith 别名):带自定义逻辑的继承属性合并

深入解析 es-toolkit 的 extendWith(assignInWith 别名):带自定义逻辑的继承属性合并 深入解析 es-toolkit 的 extendWithassignInWith 别名带自定义逻辑的继承属性合并【免费下载链接】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-toolkitextendWith是 es-toolkit 在 Lodash 兼容层es-toolkit/compat中为assignInWith提供的别名函数其核心能力是借助自定义函数customizer决定每个属性的最终取值将源对象的全部属性包括原型链上继承的属性合并进目标对象。本文将从兼容层文档出发结合 extendWith 源码、assignInWith 实现 与对应测试讲清它的签名、三种典型用法、customizer 的回退语义、参数细节以及官方建议谨慎使用的性能原因帮助你安全地把 Lodash 的_.extendWith迁移到 es-toolkit。extendWith 是什么assignInWith 的别名在兼容层文档中extendWith.md 是一页非常精简的别名说明页它通过 frontmatter 的redirect: /ja/compat/reference/object/assignInWith.html直接重定向到 assignInWith 的文档页正文只有一句话——extendWith是assignInWith的别名详细说明请参考 assignInWith 文档。这与源码中的实现完全一致extendWith.ts 只有一行导出语句export { assignInWith as extendWith } from ./assignInWith.ts;也就是说extendWith与assignInWith是同一个函数对象二者在类型签名、行为与性能上完全等价。对应的单元测试 extendWith.spec.ts 也直接验证了这一点import { assignInWith } from ./assignInWith; import { extendWith } from ./extendWith; it(should be an alias of assignInWith, () { expect(extendWith).toBe(assignInWith); });因此本文剩余部分以assignInWith的完整文档assignInWith.md为主体展开你在代码中无论写extendWith还是assignInWith得到的都是同一份实现。为什么官方建议谨慎使用assignInWith.md 开头就给出了醒目的 warning这个assignInWith函数由于要处理继承的属性、并逐一调用 customizer 函数逻辑复杂且运行较慢。建议改为使用Object.assign并直接内联实现自定义逻辑。这是理解本函数定位的关键背景它属于 Lodash 兼容层es-toolkit/compat设计目标是让既有 Lodash 代码能够平滑迁移而非在性能敏感路径上作为首选工具。如果你的场景不需要「继承属性」和「逐属性自定义」直接使用原生Object.assign或 es-toolkit 的assign会更加高效。具体多出的开销有两处后面在源码剖析中会逐一印证通过keysIn遍历原型链而不是仅仅Object.keys对每个属性执行eq值比较与 customizer 调用。基本用法与完整示例函数签名如下const result assignInWith(target, ...sources, customizer);它使用 customizer 函数将源对象的所有属性包括继承属性赋给目标对象。核心语义是customizer 函数决定每个属性的最终值。示例一仅在属性为 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; }); // 结果: { a: 1, b: 2, c: 3 }这是最常见的「防覆盖」模式目标对象中已存在的值a: 1保持不变只有目标中为undefined的属性b才接受源对象的值而目标中不存在的属性c会被直接补上。示例二用 customizer 合并数组import { assignInWith } from es-toolkit/compat; const target { numbers: [1, 2] }; const source { numbers: [3, 4], name: test }; assignInWith(target, source, (objValue, srcValue) { if (Array.isArray(objValue) Array.isArray(srcValue)) { return objValue.concat(srcValue); } return srcValue; }); // 结果: { numbers: [1, 2, 3, 4], name: test }当目标与源的同名属性都是数组时customizer 返回拼接后的新数组其他情况如name则回退为直接采用源值。这正是「逐属性自定义合并策略」的典型用途也是普通Object.assign无法直接做到的。示例三连继承属性一起处理import { assignInWith } from es-toolkit/compat; function Parent() {} Parent.prototype.inherited value; const child Object.create(Parent.prototype); child.own ownValue; const target { existing: data }; assignInWith(target, child, (objValue, srcValue, key) { if (objValue undefined) { return processed_${srcValue}; } return objValue; }); // 结果: { existing: data, own: processed_ownValue, inherited: processed_value }注意第三个参数keycustomizer 还能拿到当前处理的属性名据此实现更精细的分支逻辑。与assign/Object.assign只复制自身属性不同这里child原型链上的inherited也被复制并经过processed_前缀处理。customizer 返回 undefined 时的回退行为一个容易踩坑但非常关键的语义当 customizer 返回undefined时函数会回退到默认的赋值行为直接采用源值。这与 Lodash 的assignWith系列约定一致目的是允许 customizer 只做「选择性拦截」其余属性交给默认逻辑。文档同时指出与assignIn相比本函数的差异在于可以对每个属性应用自定义逻辑而assignIn只能整体使用默认赋值规则。参数与返回值参数类型说明targetany属性被复制到的目标对象会被原地修改...sourcesany[]提供属性的源对象可传多个自身属性与继承属性都会被复制customizerfunction决定每个属性最终取值的函数形如(objValue, srcValue, key, object, source) any返回undefined时使用默认赋值行为返回值any—— 返回修改后的目标对象本身即target不是新对象与Object.assign一致。customizer 的五个形参依次为目标对象当前值objValue、源对象当前值srcValue、属性名key、目标对象object、源对象source。前三个参数在实际使用中最为常用。源码级原理剖析1. 入口customizer 的提取与多源循环assignInWith.ts 的运行时实现很直白export function assignInWith(object: any, ...sources: any[]): any { let getValueToAssign sources[sources.length - 1]; if (typeof getValueToAssign function) { sources.pop(); } else { getValueToAssign undefined; } for (let i 0; i sources.length; i) { assignInWithImpl(object, sources[i], getValueToAssign); } return object; }关键逻辑是通过「最后一个参数是否为函数」来判定 customizer 是否存在。因此 customizer 必须作为最后一个参数传入如果省略则退化为普通的多源赋值。随后对每个源对象依次调用assignInWithImpl按传入顺序覆盖后面的源对象优先级更高。2. 单源合并keysIn customizer eq 优化assignInWithImpl 是逐属性赋值的核心function assignInWithImpl(object, source, getValueToAssign) { const keys keysIn(source); for (let i 0; i keys.length; i) { const key keys[i]; const objValue object[key]; const srcValue source[key]; const newValue getValueToAssign?.(objValue, srcValue, key, object, source) ?? srcValue; if (!(key in object) || !eq(objValue, newValue)) { object[key] newValue; } } }这里可以拆出三个值得注意的实现细节keysIn提供全部键见 keysIn.ts它用for...in收集包括原型链在内的所有可枚举字符串键对数组、类数组、Buffer、TypedArray、原型对象还有专门分支。这就是「连继承属性一起复制」的实现来源也是比Object.assign慢的原因之一。?? srcValue实现回退语义getValueToAssign?.(...) ?? srcValue正是文档所述「customizer 返回undefined时采用默认赋值」的代码体现?.保证 customizer 缺失时直接取源值。eq避免无意义写入只有当属性在目标中不存在或者新旧值经eq判定不同才写回。eq见 eq.ts采用SameValueZero语义0与-0相等、NaN与NaN相等这既是一种赋值优化也保证了与 Lodash 的行为兼容。3. 丰富的 TypeScript 重载assignInWith.ts 为 0 到 4 个源对象提供了 5 组重载签名分别返回T U、T U V……以及一个处理任意参数数量的兜底重载assignInWithR(object: any, ...otherArgs: any[]): R。这意味着在 TypeScript 项目中可以享受精确的合并类型推导由于extendWith是同一函数的别名导出这些重载对extendWith同样生效。测试用例验证除了别名测试assignInWith.spec.ts 还覆盖了三个行为要点// 1. customizer 按规则拦截已有值保留缺失值补上 const actual assignInWith({ a: 1, b: 2 }, { a: 3, c: 3 }, (a, b) (a undefined ? b : a)); // { a: 1, b: 2, c: 3 } // 2. customizer 返回 undefined 时回退到默认赋值noop 恒返回 undefined assignInWith({}, expected, noop); // 等价于直接拷贝 // 3. 不带 customizer 时退化为普通多源赋值 assignInWith(target, source); // { a: 1, b: 2 }这三条用例正好对应本文前面讲解的回退语义、参数提取与基础赋值行为可以作为迁移时对照验证的参考。与其他相关函数的对比与选型建议extendWith/assignInWith复制自身 继承属性且逐属性应用 customizer。功能最全开销最大。assignIn别名extend同样复制继承属性但没有 customizer只能整体使用默认赋值源码 assignIn.ts 与assignInWith共用keysIn与eq优化实现更简单。原生Object.assign/ es-toolkit 的assign只复制自身可枚举属性性能最好但没有 customizer 也没有继承属性支持。选型建议如果是从 Lodash 迁移且确实依赖「继承属性 自定义合并」直接用extendWith保证行为一致如果只是普通对象合并优先使用Object.assign或 assign 系列的轻量版本避免为用不到的特性付出性能代价。延伸阅读完整 API 文档assignInWith (Lodash 互換性)、assignIn (Lodash 互換性)实现源码extendWith.ts、assignInWith.ts、assignIn.ts依赖的内部工具keysIn.ts、eq.ts测试用例extendWith.spec.ts、assignInWith.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),仅供参考
返回列表