es-toolkit set 深度指南:3 种路径语法写入嵌套对象,动态 key 一步到位
2026/9/16 17:56:54 网站建设 项目流程

es-toolkit set 深度指南:3 种路径语法写入嵌套对象,动态 key 一步到位

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

数据 key 从后端配置拼出来时,"往user.address.city写个值"这种需求,用obj?.a?.b = v一行都写不出——层数在编译期根本定不了。es-toolkit 兼容层的set就是为这类场景准备的:接收一个动态路径字符串,在任意深度写入值,缺什么节点就自动补什么节点。

快速上手:8 行代码跑通最小用例

import { set } from 'es-toolkit/compat'; const config = {}; set(config, 'user.profile.city', '上海'); console.log(config); // { user: { profile: { city: '上海' } } }

config原本只是个空对象,set沿着'user.profile.city'逐层建好容器,把值写进最深处。实现上它只是 src/compat/object/set.ts 里 5 行的薄壳,真正的活都交给了updateWith

export function set(obj, path, value) { return updateWith(obj, path, () => value, () => undefined); }

高频场景

场景 1:路径来自运行期,写死赋值不现实

表单字段映射、远端下发的配置项,路径只能运行期才知道:

const field = request.body.target; // 例如 'theme.mode' set(state, field, 'dark');

原理一句话:字符串路径先被toPath拆成段数组(点号、括号都是分隔符),再逐段走树。

场景 2:从零开始建结构,连"下标跳跃"都帮你兜住

const doc = {}; set(doc, 'items[1].price', 42); // { items: [undefined, { price: 42 }] }

中间节点不存在时,set会看下一段是不是下标:是就建数组[],否则建对象{}(判断逻辑在 src/compat/_internal/isIndex.ts)。注意这里跳过下标 0,items[0]是空洞——这是稀疏数组,for...of遍历时会被跳过,别意外。

场景 3:键名本身带点号,用括号或数组路径绕过

set(obj, 'a["b.c"].d', 1); // 引号里的点不拆分 → obj.a['b.c'].d set(obj, ['a.b'], 2); // 数组元素按字面键 → obj['a.b']

两条规则都指向同一个结论:字符串路径里"带引号或带括号的段"和"数组形式的单个元素"都当作字面键名,不做二次拆分。这在键名来自外部输入、无法预测时能救命。

场景 4:往数组里按位置改值

const arr = [1, 2, 3]; set(arr, 1, 4); console.log(arr); // [1, 4, 3]

数字直接当下标用,不需要字符串包装;对象套数组、数组套对象这种混合嵌套同样支持,比如set({}, 'users[0].name', 'a')会得到{ users: [{ name: 'a' }] }

⚠️ 容易踩坑的隐性规则

以下行为都有测试用例背书(见 src/compat/object/set.spec.ts),迁移 lodash 代码时建议逐条过一遍:

隐性规则输入 → 输出为什么
原地修改,返回同一个引用const r = set(o, 'y', 2)r === otrueupdateWith全程在改原对象,最后原样返回,不做拷贝
数字下标建数组,其余建对象set({}, 'a[1].b', 1){ a: [undefined, { b: 1 }] }下一段通过isIndex判定(非负整数,或/^(?:0\|[1-9]\d*)$/字符串)才建[]
nullish 输入静默返回set(null, 'a.b', 1)返回null,不抛错函数入口先判空直接 return
危险键名静默中止set({}, 'a.__proto__.x', 1)不写任何东西每段键名都过isUnsafeToWriteProperty,命中__proto__/constructor/prototype立即中止(见 src/_internal/isUnsafeToWriteProperty.ts)
中间是基本类型会被整个覆盖set({ a: '' }, 'a.b', 2){ a: { b: 2 } }中间节点不是对象就无条件重建容器,原值直接丢弃

额外一个细节:写入前会用eq比较目标位置的值,相同就跳过赋值,不会触发多余的 setter(测试里专门给带 setter 的属性做了断言)。

性能与替代方案

官方文档在 docs/compat/reference/object/set.md 开头就给了警告:set走完整路径解析 +get读旧值 + 逐段建容器 + 逐段赋值的流水线,速度较慢,优先用直接赋值或解构赋值。

需求现代语法set
路径编译期已知obj.a.b.c = 1(直接赋值,最快)能做但纯属杀鸡用牛刀
不可变更新{ ...obj, a: { ...obj.a, b: 1 } }做不到(它本来就改原对象)
路径运行期才知道得手写递归写入✅ 核心用途
中间节点缺失需自动创建手写reduceObject.assign✅ 内置
lodash_.set存量代码迁移需重写✅ 行为对齐

结论很直白:只要路径在编译期能写死,就该放弃set。它的价值全部押在"路径是动态的"这一条上,为此付出的代价是每次调用都多一整套解析和判空逻辑。

🛠️ 进阶延伸

默认"下标→数组、其余→对象"的建容器策略不够用时,换setWith——它多收一个 customizer 回调,返回undefined时回落到默认策略:

import { setWith } from 'es-toolkit/compat'; const obj = {}; setWith(obj, '[0][1]', 'v', () => []); // { '0': [undefined, 'v'] }

反过来,set就是"customizer 恒返回undefined"的setWith特例(见 src/compat/object/setWith.ts)。读取侧配套的是get(obj, 'a.b.c', 'default'),同样支持路径语法且 null 安全。

决策清单

  • 该用:路径是动态字符串(用户输入 / 配置下发 / lodash 迁移)
  • 该用:需要"缺失节点自动创建"且不想手写递归
  • 不该用:路径编译期已知,直接obj.a.b = 1更快更省
  • 不该用:需要不可变语义——set永远原地修改并返回原引用
  • 不该用:对null/undefined期望有副作用——它静默返回原值,错误会被吞掉

一句话总结set是动态路径写入的瑞士军刀,不是性能工具。

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询