ARTICLE DETAIL

资讯详情

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

es-toolkit 兼容层 set 函数深度解析:按路径写入对象、自动创建嵌套结构与源码实现

es-toolkit 兼容层 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-toolkitset是 es-toolkit 兼容层es-toolkit/compat中与 lodash_.set对齐的对象工具函数允许通过字符串路径、数组路径或括号语法在对象的任意深度位置写入值const result set(obj, path, value)。它从兼容入口导入import { set } from es-toolkit/compat。⚠️ 这个set函数内部调用updateWith函数由于复杂的路径处理和对象创建逻辑运行速度较慢。请改用更快、更现代的直接赋值direct assignment或解构赋值destructuring assignment。仅当路径是动态拼接的、或需要与 lodash 行为完全对齐的自动创建语义时set才是合理选择编译期已知的固定路径应直接写obj.a.b.c value。一、API 速览函数签名定义于 src/compat/object/set.tsexport function setT extends object(object: T, path: PropertyPath, value: any): T;导入方式import { set } from es-toolkit/compat;参数类型说明objectT要设置值的对象会被原地修改pathPropertyPath属性路径支持字符串、数组、数字或 symbolvalueany要设置的值返回值返回原对象的同一引用非拷贝set(obj, a, 1) obj恒为true。关键公共类型PropertyPath定义于 src/compat/_internal/PropertyPath.tsimport { Many } from ./Many.ts; export type PropertyPath ManyPropertyKey;ManyT即T | T[]因此PropertyPath可以是单个PropertyKeystring | number | symbol或其数组。二、场景化示例嵌套对象写入路径中任何不存在的中间节点会被自动创建为普通对象import { set } from es-toolkit/compat; const obj { a: { b: { c: 3 } } }; set(obj, a.b.c, 4); console.log(obj.a.b.c); // 4数组下标与稀疏数组数字下标会创建数组容器跳过中间下标时产生稀疏数组import { set } from es-toolkit/compat; const list: Recordstring, unknown {}; set(list, items[0], first); set(list, items[2], third); console.log(list); // { items: [first, empty, third] }混合嵌套结构对象含数组、数组含对象、多层交叉均可正确处理import { set } from es-toolkit/compat; const complex: Recordstring, unknown {}; set(complex, users[0].profile.settings.theme, dark); console.log(complex); // { users: [{ profile: { settings: { theme: dark } } }] }数组形式路径与字面键名数组路径不会被 join 成字符串对象已存在a.b字面键时a.b按字面键名处理而非路径拆分import { set } from es-toolkit/compat; const object { a.b: 1, a: { b: { c: 1 } } }; set(object, [a, b, c], 99); console.log(object.a.b.c); // 99 const literal { a.b: 1 }; set(literal, a.b, 2); console.log(literal[a.b]); // 2写入字面键名 a.b原地修改语义set是原地修改in-place mutation函数返回值与传入对象是同一引用。src/compat/object/set.spec.ts 中expect(actual).toBe(object)直接断言了这一点import { set } from es-toolkit/compat; const original { x: 1 }; const result set(original, y, 2); console.log(original result); // true console.log(original); // { x: 1, y: 2 }需要不可变更新时必须自行深拷贝后再写入或使用解构展开。三、源码执行流水线set本身是极薄的委托壳全部逻辑在 src/compat/object/updateWith.ts 中// src/compat/object/set.ts L89-96 export function setT extends object(obj: T, path: PropertyPath, value: any): T { return updateWith(obj, path, () value, () undefined); }updateWith的完整执行链路拆解如下nullish 保护obj为null/undefined时直接返回原值不抛错。src/compat/object/updateWith.tsif (obj null !isObject(obj)) { return obj; }路径归一化将PropertyPath统一转为PropertyKey[]。优先检查isKey判断是否为字面键名再检查数组否则走toPath字符串解析。src/compat/object/updateWith.ts解析器实现在 src/compat/util/toPath.tslet resolvedPath: PropertyKey[]; if (isKey(path, obj)) { resolvedPath [path]; } else if (Array.isArray(path)) { resolvedPath path; } else { resolvedPath toPath(path); }读取当前值并计算新值通过get(obj, resolvedPath)读取路径处的旧值交给 updater 产生新值。set传入的 updater 是() value恒定返回目标值。src/compat/object/updateWith.tsconst updateValue updater(get(obj, resolvedPath));逐段遍历与中间容器创建从根对象出发逐段推进。每段先用toKey归一化键名再经过isUnsafeToWriteProperty安全检查。非末段时若现有值不是对象则根据下一段是否为数组下标决定创建[]还是{}。isIndex判断逻辑见 src/compat/_internal/isIndex.tsconst IS_UNSIGNED_INTEGER /^(?:0|[1-9]\d*)$/; export function isIndex(value: PropertyKey): boolean { switch (typeof value) { case number: return Number.isInteger(value) value 0; case string: return IS_UNSIGNED_INTEGER.test(value); default: return false; } }不安全属性中止任何路径段命中__proto__、constructor、prototype时立即返回原对象阻断原型污染写入。src/_internal/isUnsafeToWriteProperty.tsexport function isUnsafeToWriteProperty(key: PropertyKey) { return key __proto__ || key constructor || key prototype; }赋值与相等性跳过通过assignValue写入内部用eq比较目标位置已有值与新值相同则跳过赋值避免触发不必要的 setter。src/compat/_internal/assignValue.tsexport const assignValue (object: any, key: PropertyKey, value: any): void { const objValue object[key]; if (!(Object.hasOwn(object, key) eq(objValue, value)) || (value undefined !(key in object))) { object[key] value; } };返回原对象遍历结束后返回obj与传入引用一致。src/compat/object/updateWith.tstoPath解析规则汇总src/compat/util/toPath.ts 将字符串路径解析为段数组路径写法解析结果备注a.b.c[a, b, c]点号分隔a[b][c][a, b, c]括号语法a[b.c].d[a, b.c, d]引号内的点号不拆分[-1.23][-1.23]括号内数字保留整体.a.b.c[, a, b, c]前导点号产生空段a..b[a, , b]连续点号产生空段[]空字符串路径四、边界行为契约以下行为均来自 src/compat/object/set.spec.ts 的测试断言nullish 输入不抛错set(null, a.b, 1)返回nullset(undefined, a.b, 1)返回undefined。 → set.spec.ts#L207-L223-0与0符号保持set(object, -0, 2)写入的键是-0而非0与 lodash 行为一致。 → set.spec.ts#L102-L113symbol 键路径unset可删除 symbol 键属性symbol in object为false。 → set.spec.ts#L115-L122跳过相同值赋值目标位置已有值且eq相等时不触发 setter避免副作用。 → set.spec.ts#L241-L259数字开头非下标键创建对象而非数组set(object, [1a, 2b, 3c], v)产出{ 1a: { 2b: { 3c: v } } }不创建数组。 → set.spec.ts#L234-L239空括号行为set(object, a[], v)产出{ a: { : v } }空括号不忽略。 → set.spec.ts#L151-L156空路径行为set(object, , v)产出{ : v }set(object, [], v)产出{ : v }。 → set.spec.ts#L158-L174覆盖基本类型中间节点set({ a: }, a.b, 2)将空字符串覆盖为{ b: 2 }。 → set.spec.ts#L225-L232字面键名优先于路径拆分对象已有a.b键时set(object, a.b, v)写入字面键而非拆分路径。 → set.spec.ts#L134-L142五、生态关联setWith自定义中间节点创建策略setWith是set的扩展版本多接收一个customizer回调用于定制路径中间节点的创建方式。差异在于set固定采用下标→数组、其余→对象策略setWith允许你接管这一决策。import { setWith } from es-toolkit/compat; const obj: Recordstring, unknown {}; setWith(obj, [0][1], value, (nsValue, key) typeof key string /^\d$/.test(key) ? {} : undefined ); // { 0: { 1: value } } —— 数字键创建对象而非数组源码中setWith仅将 customizer 透传给updateWithsrc/compat/object/setWith.ts。set等价于 customizer 恒返回undefined的setWith退化形式。get配套的路径读取函数get使用完全相同的路径语法读取对象值支持默认值参数是set内部写入前读取旧值的同一函数。import { get } from es-toolkit/compat; const object { a: { b: { c: 3 } } }; get(object, a.b.c); // 3 get(object, a.b.d, default); // default get(null, a.b.c, fallback); // fallbacknull 安全实现位于 src/compat/object/get.ts。六、选型决策推荐使用场景动态路径写入路径来自配置、用户输入或运行期拼接编译期无法确定无法写成obj.a.b.c value字面量。lodash 迁移替换存量代码中_.set调用可直接替换为es-toolkit/compat的set路径解析、自动创建、稀疏数组、-0符号等行为与 lodash 对齐。复杂路径字符串需要处理括号语法、引号转义、带点号的键名等 lodash 兼容路径写法如a[b.c].d。自定义节点创建使用setWith接管中间容器类型满足特定数据结构的构造需求。建议改用现代语法的场景路径编译期已知直接写obj.a.b.c value零开销。需要不可变更新使用解构赋值{ ...obj, a: { ...obj.a, b: value } }不修改原对象。读取操作优先使用可选链obj?.a?.b?.c无需引入兼容层。简单数组赋值arr[1] value即可无需路径解析流水线。set的正确价值在于兼容性与动态路径能力而非性能。对从 lodash 迁移的存量代码es-toolkit/compat中的set提供了与_.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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表