
从 lodash 迁_.set到 es-toolkit到底会踩什么坑对象路径写入 set 全解析【免费下载链接】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从 lodash 迁_.set到 es-toolkit 兼容层我第一个坑是当时想写入的对象恰好是nullset没抛错——它悄悄返回了原值我要写的那个值无声无息地丢了。在细看这个写对象路径任意深度的函数之前先把它的行为契约讲清楚再说成本花在哪。set的行为契约迁移前必须明确的三条它做的事很单纯在指定对象路径上写入值路径中缺失的中间节点自动补建数字下标补数组其余补对象。最小示例覆盖两个核心行为import { set } from es-toolkit/compat; const obj { a: { b: { c: 3 } } }; set(obj, a.b.c, 4); // obj.a.b.c 4 set(obj, a.x.y, v); // 自动创建 { a: { x: { y: v } } }第二条容易忽略它原地修改且返回的就是原引用。测试里专门断言toBe(object)而不是toEqual见 src/compat/object/set.spec.ts L92–L100const original { x: 1 }; const result set(original, y, 2); original result; // true不是拷贝想要不可变更新别指望它帮你拷贝解构更直接。第三条就是开头的坑对象为null/undefined时set原样返回、不抛错同文件 L207–L223 有断言守卫代码在 updateWith 的 L81–L83。配合会返回 null 的异步 fetch数据丢失是完全无声的。参数速查名称类型说明objectT目标对象原地修改pathPropertyPath路径字符串 / 键数组 / 键写法见下文valueany要写入的值返回值T就是传入的那个原对象一句话带走set写的是动态路径版的obj.a.b v代价是原地改、null 输入静默跳过。⚠️ 为什么我按路径写完数组变稀疏了按数字下标写入、中间下标又缺着的时候你会得到一个稀疏数组——这是设计使然不是 bugconst list {}; set(list, items[0], first); set(list, items[2], third); console.log(list); // { items: [first, undefined, third] }行为的来源在补建节点的判断上updateWith建中间容器时看一眼下一段路径——是不是数组下标辅助函数isIndex的正则/^(?:0|[1-9]\d*)$/即非负整数形态是就建[]不是就建{}。没写到的下标就是空槽0 in items会是falsespec L201 有断言。避坑矩阵上线前对照的五条边界行为场景实际行为依据数字下标跳号写入产出稀疏数组spec L193–L205中间节点是基本类型如空字符串直接覆盖成新容器spec L225–L232键长得像数字但非下标如[1a,2b]建普通对象而非数组spec L234–L239写入值与现有值相同跳过真实赋值不触发 setterspec L241–L259对象为 nullish原样返回不抛错spec L207–L223最后一条我自己也没料过给属性defineProperty一个 setter 后写入相同的值不会触发它——这是逐段赋值前做相等判断后的有意跳过。如果你从 lodash 迁移时代码依赖 setter 打日志或做校验这里会埋暗坑。一句话带走自动建节点是好事但稀疏数组和覆盖基本类型节点这两条记得写进你的 code review 检查项。 对象路径的三种写法点号、括号、数组对照path接受三种形态解析方式各不相同写法示例解析结果点号字符串a.b.c[a,b,c]括号字符串a[b][c]、a[b.c].d[a,b,c]引号内的点不拆分数组[nested,array,0]原样使用不会被 join 成字符串字符串路径交给toPath解析几条规则值得记住点是分隔符但引号里的点不拆a[b.c].d→[a,b.c,d]括号里的小数整体保留[-1.23]不会拆源码中的bracketNumberRegex负责这条前导点产生空段.a.b→[,a,b]连续点a..b→[a,,b]这里有个不太显眼的坑字符串a.b和数组[a.b]都会被当成一个字面键名写到名为a.b的属性上而数组[a,b]才是两层嵌套spec L134–L149 有断言。lodash 兼容语义就是如此迁移时那些键名里真带点号的模板字符串路径要特别留意。一句话带走路径拿不准时优先写成[a,b]数组形式——不走解析零意外。 一次set调用内部实际跑了几步set本体一口气能读完本质是 4 行委托src/compat/object/set.ts L89–L96export function setT extends object(obj: T, path: PropertyPath, value: any): T { return updateWith(obj, path, () value, () undefined); }逻辑全在 src/compat/object/updateWith.ts一次调用走完五步L81–L127空值守卫对象为 nullish直接返回原值归一化路径单键直接用、数组原样用、字符串走toPath先用get读当前值交给 updaterset传的是恒定返回value的函数逐段走路径已有对象就复用缺了就按下一段是不是下标决定建数组还是对象逐段赋值值没变就跳过。第 4 步里还夹着一道安全线路径段一旦是__proto__、constructor、prototype写入直接中止并返回原对象L101–L103 调用isUnsafeToWriteProperty这是防原型污染的保护行为与 lodash 对齐。一句话带走set的成本在读 → 判断 → 建容器 → 写这套完整流程——路径静态可写的时候这些开销全是白花。 官方说慢依据是什么什么情况下能接受文档的警告是最直白的一份表述docs/compat/reference/object/set.mdThissetfunction internally calls theupdateWithfunction and operates slowly due to complex path processing and object creation logic. Use faster and more modern direct assignment or destructuring assignment instead.依据就是上一节的流程拆解每次调用都要付路径解析、读旧值、逐段判断容器的钱循环里处理上千条记录时会被成倍放大。所以边界很清楚路径静态可知 →obj.a.b v没有讨论空间需要不可变语义 →{ ...obj, a: { ...obj.a, b: v } }只有两种情况值得付这份开销路径是运行时拼出来的来自配置、表单、用户输入或需要一次调用补全缺失的中间节点、且要求与 lodash 行为逐条对齐迁移场景。一句话带走set的价值在兼容性与动态路径不在性能——别用它省打字用它兜住写不出来的路径。 30 秒决策set到底要不要用路径静态可知路径运行时拼装可原地修改直接obj.a.b v用兼容层的set要新引用解构后赋值解构 手动递归或接受set的原地修改延伸阅读默认的下标建数组、其余建对象策略不满足时看setWith多一个 customizer 回调决定中间节点类型只读值的话get支持同样的路径语法还能带默认值。【免费下载链接】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),仅供参考