es-toolkit set 函数一篇讲透:路径写入、自动建嵌套、源码级避坑指南
2026/9/17 4:17:52 网站建设 项目流程

es-toolkit 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

把老项目从 lodash 迁到 es-toolkit 时,很多人会卡在这种地方:大部分函数换个 import 就完事,唯独_.set让人犹豫——路径往往是运行时拼出来的(比如_.set(obj, field + '.value', v)),没法直接赋值。

好消息是,es-toolkit set 函数(来自 compat 兼容层)就是 lodash set 替代的现成答案,行为与_.set几乎逐行对齐,可以整段照搬:

import { set } from 'es-toolkit/compat'; const config = {}; set(config, 'server.port', 8080); set(config, ['cache', 'ttl'], 3600);

但在你开始全局替换之前,先花三分钟搞清楚它到底怎么工作、坑藏在哪。

es-toolkit compat set 快速上手:三种路径写法

签名一行说清:

set(object, path, value)—— 往objectpath位置写value,返回修改后的原对象本身。

path的类型是PropertyPath(定义在 src/compat/_internal/PropertyPath.ts),白话解释:可以是单个键(字符串/数字/symbol),也可以是键的数组,三种写法都能用:

// 点号写法 set(obj, 'a.b.c', 4); // 括号写法 set(obj, 'users[0].name', 'John'); // 数组写法 set(obj, ['nested', 'array', 0], 'first');

它的核心能力是按路径写入对象时自动创建嵌套结构:路径中间哪一段不存在,就自动补哪一段;而且补出来的容器类型是智能的——下一段是数组下标就建数组,否则建普通对象。

const empty = {}; set(empty, 'user.profile.name', 'John'); console.log(empty); // { user: { profile: { name: 'John' } } }

这正是它和obj.a.b = v拉开差距的地方:直接赋值在这里会直接抛Cannot set properties of undefined

set 的路径到底怎么拆:toPath 解析规则表

所有字符串路径都会过一遍 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']引号内容不拆
'a..b'['a', '', 'b']连续点号产生空段
'.a.b'['', 'a', 'b']前导点号是空键
'items[0]'['items', '0']下标以字符串保留

有两个反直觉的点,es-toolkit 路径解析里最容易理解错:

  • 含点号的字符串不一定被拆isKey判定它是"普通键"时(比如把'a.b'当单个键名),就不会拆。所以set(obj, 'a.b')set(obj, ['a.b'])效果相同——都是写一个字面键名为a.b的属性。
  • 数组路径不走字符串解析,原样使用。当键本身带点号、空格等特殊字符时,数组写法是最稳的选择。

set 行为避坑清单:原地修改、稀疏数组与 null 安全

这部分是线上最容易出问题的地方。⚠️

原地修改,不是不可变更新。

const original = { x: 1 }; const result = set(original, 'y', 2); original === result; // true,同一个引用

set直接改你传进去的对象。如果你的状态管理(Redux、Zustand 之类)靠引用变化触发更新,一次set会让 UI 完全无感。需要不可变语义时,用解构赋值,或者先克隆再写。

跳号下标会造出稀疏数组。

const list = {}; set(list, 'items[0]', 'first'); set(list, 'items[2]', 'third'); console.log(list.items); // ['first', <空位>, 'third']

中间下标不会被填undefined,而是留空洞(JSON.stringify会输出null)。下游代码若假定密集数组,遍历和索引都会对不上。

中间节点是基本类型时,直接覆盖。比如set({ a: '' }, 'a.b', 2)会得到{ a: { b: 2 } }——原值被容器对象顶掉,不报错也不提醒。

null 安全。对象参数传null/undefined时不抛错,原样返回。防御性写法很友好,但要注意"写入"其实什么都没发生。

原型污染防线。路径里出现__proto__这类危险键时,函数直接中止并返回原对象,污染不了原型链。这是它比手写obj[key] = value循环多出来的一道安全保险。

updateWith 流水线到底在干什么:set 为什么慢

源码视角看,src/compat/object/set.ts 的全部实现就是 4 行委托:

export function set<T extends object>(obj: T, path: PropertyPath, value: any): T { return updateWith(obj, path, () => value, () => undefined); }

也就是说,每次调用的开销全在 src/compat/object/updateWith.ts 的流水线上:

  1. 路径归一化:先试isKey判定,再试数组,最后才走toPath字符串解析;
  2. 先用get读当前值、再跑 updater 算新值——而set传的 updater 是恒返回目标值的箭头函数,这一步纯属陪跑;
  3. 逐段遍历:每段过一遍toKey归一化和isUnsafeToWriteProperty安全检查,非末段还要看下一段是不是下标(src/compat/_internal/isIndex.ts:非负整数或匹配^(?:0|[1-9]\d*)$的字符串)来决定建[]还是{}
  4. 每段写入都走assignValue,里面还有"值相同就跳过赋值"的优化,避免触发多余的 setter。

上图是 es-toolkit 与 lodash 在其他函数上的性能对比——库的核心卖点是快。但set住在兼容层里,为了和 lodash 行为 100% 对齐,上面这套"兼容工序"一段都不能省。所以官方文档开头就挂了警告:路径在编译期已知时,直接用直接赋值或解构赋值,比 set 更快

set 和直接赋值怎么选:场景对照表

最后给一张决策表。判断逻辑一句话:路径定死就别用 set,路径动态且要自动建结构,set 才是正解。

场景推荐方案理由
路径编译期固定obj.a.b.c = value零开销,可读性最好
需要不可变更新{ ...obj, a: { ...obj.a, b: value } }触发引用变化,状态管理友好
只读不写obj?.a?.b?.cget可选链是原生方案
路径动态(配置/用户输入/运行时拼接)es-toolkit/compatset直接赋值做不到,且需自动建结构
lodash 存量代码迁移期set/setWith_.set/_.setWith行为逐行对齐
要自定义中间容器(Map、类实例等)setWith+ customizer默认的"下标建数组、其余建对象"不够用时

✅ es-toolkit compat set 的定位一句话:它解决的是兼容性与动态路径问题,不是性能问题。迁移存量代码用它替换_.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),仅供参考

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

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

立即咨询