Lodash — 以 iteratee 与 wrapper 组织的通用工具函数集
已复核Lodash 是一个围绕数组、对象、字符串和函数的 JavaScript 工具库。日常类比:工具墙上一整排扳手,每把都能单独拿;需要连续加工时,也可以先把材料放进托盘(wrapper),走完再倒出来。
你写:
var _ = require('lodash');
_.map(users, 'name');_.get(user, 'profile.city', 'unknown');var later = _.debounce(save, 250);_.map 的第二个参数可以是函数、属性名或匹配对象;_.get 按路径取值;_.debounce 返回带 cancel / flush 的延迟函数。固定 4.18.1 源码仓以 lodash.js 作为 UMD 主文件,并另有 lodash/fp 构建。
不理解 lodash 的 iteratee、wrapper 和“哪些方法会改原对象”,就解释不了下面几件事:
- 为什么
_.map(users, 'name')能当user => user.name用 - 为什么
_([1, 2, 3]).map(n => n * 2)还不是数组,必须.value() - 为什么
_.set(object, 'a.b', 1)会改传入的object - 为什么
lodash/fp的参数顺序和是否改原值,与默认构建不一致
固定 4.18.1 的主链可以拆成五步:
-
按需取方法或取全集:
require('lodash')拿 UMD 全集;require('lodash/map')只取单方法;require('lodash/fp')走 immutable / 自动柯里 / iteratee-first / data-last 的 FP 构建。 -
规范化 iteratee:
baseIteratee把函数原样留下、null/undefined当成 identity、数组当成matchesProperty、普通对象当成matches、其余当成property。 -
可选 wrapper:
_(value)生成LodashWrapper;_.chain(value)额外把__chain__设为 true。链式方法先记入 wrapper,不立刻算出最终数组。 -
惰性求值:部分序列方法有
LazyWrapper对位。isLaziable检查同名方法是否挂在LazyWrapper.prototype;真正取值走wrapperValue→baseWrapperValue。 -
按方法决定是否改原值:
_.set/_.assign/_.pull等会改入参;_.cloneDeep、_.map等返回新值。FP 构建用fp/_mapping.js的mutate表把会改原值的方法转成不可变变体。
案例 1:iteratee 三种写法指向同一条规范化路径
Section titled “案例 1:iteratee 三种写法指向同一条规范化路径”var users = [ { name: 'Ada', active: true }, { name: 'Bob', active: false }];
_.map(users, function(user) { return user.name; });_.map(users, 'name');_.filter(users, { active: true });_.find(users, ['name', 'Ada']);属性字符串走 property,对象走 baseMatches,['name', 'Ada'] 走 baseMatchesProperty。它们都先进入 baseIteratee,不是三套独立实现。
案例 2:隐式包装要 .value() 才出数组
Section titled “案例 2:隐式包装要 .value() 才出数组”var squares = _([1, 2, 3]).map(function(n) { return n * n; });Array.isArray(squares); // falseArray.isArray(squares.value()); // truelodash(value) 见到普通对象且不是数组、也不是 LazyWrapper 时,会新建 LodashWrapper。中间步骤保存 __wrapped__ 与 __actions__;value() 才展开。
案例 3:debounce 默认只在尾沿触发,throttle 复用同一套实现
Section titled “案例 3:debounce 默认只在尾沿触发,throttle 复用同一套实现”var save = _.debounce(writeDraft, 250);var ping = _.throttle(heartbeat, 1000);
save.cancel();ping.flush();debounce 默认 leading = false、trailing = true,可用 maxWait 封顶等待。throttle(func, wait) 直接调用 debounce(func, wait, { leading: true, maxWait: wait, trailing: true })。两者都挂 cancel 与 flush。
-
把 Lodash 当成“全部纯函数”:
_.set经baseSet原地写入;路径上的__proto__/constructor/prototype会被拒绝并原样返回对象。需要不可变更新时应看lodash/fp或 immer。 -
丢掉 wrapper 的返回值:
_(arr).map(fn)不是数组。隐式链在部分方法后会自动展开,显式_.chain则一直保持包装,直到.value()。 -
把 throttle 理解成另一套计时器:它没有独立时钟实现,只是给 debounce 加上
maxWait = wait且默认leading = true。 -
把 npm 包的
gitHead当成源码 tag:lodash@4.18.1的 npmgitHead指向4.18.1-npm发布树,lodash-es@4.18.1指向4.18.1-es。本页绑定的是源码 tag4.18.1剥皮提交。 -
把 README 的 gzip 体积当成本轮测量:文档写过 core / full build 的压缩体积,本轮未安装依赖、未打包、未测体积。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 需要路径读写、集合变换、防抖节流等一组稳定工具,并接受默认构建的 mutate 语义
- 打包器能按
lodash/<method>或lodash-es做按方法引用 - 想对照 FP 风格时,明确改用
lodash/fp,而不是混用默认参数顺序
不适用:
- 整条数据管道必须不可变、自动柯里、data-last——应直接看 ramda 或
lodash/fp - 只做日期计算——date-fns / dayjs 的合同更窄
- 需要把“看起来可变、实际不可变”写成默认写法——immer 更贴
- 不能接受源码仓
engines.node >= 4与多发布树并存的 provenance
固定版本边界
Section titled “固定版本边界”- 本文绑定
lodash/lodash@cb0b9b9212521c08e3eafe7c8cb0af1b42b6649e,源码 tag 为4.18.1,lodash.js内VERSION同为4.18.1。 - 源码仓
package.json标记private: true,main为lodash.js;npmlodash@4.18.1的gitHead是4f0b76e2eca13de1c1fe8b4305abc1f7d63f4b86(4.18.1-npm^{}),lodash-es@4.18.1的gitHead是d85490ebfb8f49ddc1eab84892aa33a3ef547894(4.18.1-es^{})。 cloneDeep使用baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG),并用Stack回指循环引用。- 本文未安装依赖、运行上游测试或测量 bundle,状态保持
UNVERIFIED。
- iteratee 是一层合同,不是语法糖清单——字符串、对象、二元数组都先规范化,再交给集合方法。
- wrapper 把“调用”和“取值”拆开——惰性与链式依赖
__actions__/LazyWrapper,不是每次 map 都立刻出数组。 - 默认构建不保证不可变——mutate 表在 FP 构建里才被统一改写。
- 发布树可以和源码 tag 不是同一提交——读 npm
gitHead前要核对应的-npm/-estag。
_.throttle(fn, 1000)内部会不会新建一套与debounce无关的计时器?_.set(object, 'a.b', 1)之后,传入的object是否保持原引用且被改写?- 不传 options 的
_.debounce(fn, 250),第一次调用会立刻执行fn吗?
检查点:
- 不会。它调用
debounce,并传入maxWait: wait。 - 会保持原引用并改写;
baseSet返回同一个object。 - 不会。默认
leading为 false,只在尾沿触发。
- 文档:lodash.com/docs
- FP 指南:lodash/lodash wiki FP-Guide
- 固定源码:lodash/lodash —— 本文绑定提交
cb0b9b9212521c08e3eafe7c8cb0af1b42b6649e - ramda —— 默认就不可变、自动柯里、data-last 的对照
- date-fns —— 同一“一功能一函数”思路,但只覆盖日期