Skip to content

指南 · 基础

版本基线 Lodash 4.18.1。本篇把「会用」推进到「懂机制」:tree-shaking 是怎么生效的、哪些方法会变异入参、iteratee 简写的三种形态、以及和原生方法的取舍边界。

一、tree-shaking 到底是怎么生效的

lodash-es 能按需打包,靠的是三件事同时成立:

  1. ESM 模块格式package.json"type": "module",每个方法是独立的 export default 模块——静态可分析的 import/export,打包器能确定「谁用了谁」。
  2. sideEffects: false:声明「仅 import 而不使用某导出时,删掉它不影响程序」,打包器据此放心摇掉未引用的方法。
  3. 具名导入import { debounce } from 'lodash-es',让打包器精确知道你只要 debounce
js
// ✅ 摇树友好:只有 cloneDeep 及其内部依赖进 bundle
import { cloneDeep } from "lodash-es";

// ❌ 整体导入 CJS lodash:全部方法进 bundle
import _ from "lodash";
_.cloneDeep(x);

三者缺一不可

即使用了 lodash-es,如果写成 import * as _ from 'lodash-es' 再到处 _.xxx,虽然技术上仍可被部分摇树,但容易让人误以为全量引入;最稳妥、最直观的还是具名导入

二、变异(mutate)还是不可变(immutable)?

这是 Lodash 最该先分清的一类陷阱:一部分方法会直接修改你传入的对象,另一部分返回新值不改原值。

会变异入参(改原对象)返回新值(不改原对象)
merge / mergeWithcloneDeep / clone
assign / assignInpick / omit
defaults / defaultsDeepmapValues / mapKeys
set / unset / updateget / has
pull / remove / fillfilter / map / uniq
js
import { merge, pick } from "lodash-es";

const a = { x: { p: 1 } };
merge(a, { x: { q: 2 } }); // a 被改成 { x: { p:1, q:2 } }
console.log(a.x); // { p: 1, q: 2 } —— a 变了!

const b = { m: 1, n: 2 };
const c = pick(b, ["m"]); // c = { m: 1 },b 不变

不可变场景别直接用变异方法

Redux reducer、Vue/React 的状态更新要求「返回新对象、不改原值」。这时不能直接 set(state, path, v) / merge(state, patch)——它们会变异 state。做法见专家篇:用 lodash/fp 的不可变版本、先 cloneDeep 再改、或用 immer。

三、iteratee 简写:三种形态

集合方法(map/filter/find/sortBy…)的回调(iteratee)支持三种简写,理解它们等价于哪个函数很重要:

js
import { find, map, filter } from "lodash-es";

// ① 字符串 → _.property 简写:取该属性值
map(users, "name"); // 等价 users.map(u => u.name)

// ② 对象 → _.matches 简写:匹配「属性全部相等」的元素
find(users, { active: true, role: "admin" }); // 找 active 且 role 匹配的

// ③ [key, value] → _.matchesProperty 简写:匹配「某属性等于某值」
filter(users, ["active", false]); // 找 active === false 的

简写让代码简洁,但要清楚它在做「取值」还是「匹配」。复杂逻辑仍传完整函数 u => ...

四、和原生方法的取舍边界

不是所有 Lodash 方法都值得用——很多已被原生平替:

需求Lodash原生平替(ES2023+)建议
遍历/映射/过滤map/filter/forEachArray.prototype.map/filter/forEach用原生
查找find/findIndexArray.prototype.find/findIndex用原生
扁平化flatten/flattenDeeparr.flat()/arr.flat(Infinity)用原生
去重(原始值)uniq[...new Set(arr)]用原生
深拷贝(无函数)cloneDeepstructuredClone看是否含函数
安全取值(静态路径)get可选链 ?. + ??用原生
深合并merge—(无原生)用 Lodash
深比较isEqual—(无原生)用 Lodash
防抖/节流debounce/throttle—(需手写,易错)用 Lodash
动态路径取值/设值get/set—(路径是变量时麻烦)用 Lodash

一句话原则

简单且原生已覆盖的 → 用原生复杂、容错要求高、原生没有的(深合并 / 深拷贝含循环引用 / 深比较 / 防抖节流 / 动态路径)→ 交给 lodash-es。

五、cloneDeep vs structuredClone

两者都是深拷贝,但能力不同:

js
import { cloneDeep } from "lodash-es";

const obj = { fn: () => 42, when: new Date(), tags: new Set([1]) };

cloneDeep(obj); // ✅ 保留函数(按引用)、Date、Set,不报错
structuredClone(obj); // ❌ 抛 DataCloneError —— 结构化克隆无法克隆函数
  • 含函数 / 类实例方法 → 用 cloneDeepstructuredClone 会抛错)。
  • 纯数据(无函数)且想零依赖 → 原生 structuredClone 即可(也支持循环引用、ArrayBuffer 等)。
  • 都比 JSON.parse(JSON.stringify(obj)) 强:后者丢函数/undefinedDate 变字符串、遇循环引用直接抛错。

进入 指南 · 进阶debounce/throttleleading/trailing/maxWaitmemoize 的缓存坑、flow 组合、get/set 与可选链的对比实战。