Lodash 工具函数速查表 - Lodash 常用方法与 JavaScript 工具库大全

这张速查给那些与其手写同样的循环、不如调用一个经过充分测试的函数的 JS 开发者。条目覆盖数组、对象、字符串、集合与函数工具,每条都给出能直接解决某个麻烦问题的调用,比如读取一个可能不存在的深层值、深拷贝以避免改到共享状态、或对输入框事件做防抖。与原生方法不同的是,Lodash 对 null/undefined 输入和边界情况更宽容。读完你应能针对去重、分组、防抖和深合并快速选对助手函数,知道哪些场景该用 Lodash 而不是手写。

编程语言·共 44 条命令·最后更新 2026-07-21
lodashjavascript工具库前端

典型使用场景

面向需要在不引入框架的前提下把数据处理写干净、同时又担心打包体积的 JavaScript 开发者。Lodash 的价值集中在四类场景:安全操作深层嵌套对象(get/set 不会因为中间层不存在而抛错)、集合化的数据处理(groupBy、keyBy、sortBy 多字段排序)、函数频率控制(debounce 用于搜索输入、throttle 用于滚动和 resize)、以及字符串与数值的小工具(camelCase、clamp、round)。本表同时保留了「哪些能被原生方法替代」的判断依据,以及按需单函数引入来压体积的实践方式,避免为了一个 debounce 把整个库打进产物。

数组 7

_.map(arr, (n) => n * 2)
把每个元素按回调变换后收集成新数组,不改动原数组
_.filter(arr, (n) => n > 0)
留下回调返回真值的元素,适合按条件筛数据
_.reduce(arr, (sum, n) => sum + n, 0)
把数组逐步累计成一个值,求和或拼字符串常用
_.chunk(arr, 2)
每 2 个一组切分,适合分页或分批处理
_.flattenDeep([1, [2, [3]]])
把任意层嵌套一维化,[1, [2, [3]]] 变成 [1, 2, 3]
_.uniq([1, 2, 1, 3])
去掉重复项,保留首次出现的顺序,得到 [1, 2, 3]
_.union([1, 2], [2, 3])
合并多个数组并按唯一值去重,得到 [1, 2, 3]

对象 7

_.get(obj, "a.b.c", "default")
安全读取深层属性,即使 a 或 a.b 不存在也不抛错,找不到时回退到默认值
_.set(obj, "a.b.c", value)
安全写入深层属性,中间层不存在会自动创建
_.pick(obj, ["name", "age"])
从对象里只取出指定键,得到新对象
_.omit(obj, ["password"])
去掉指定键(如敏感字段),保留其余所有属性
_.merge(target, source)
深合并对象,嵌套对象按层合并而非整体覆盖
_.cloneDeep(obj)
递归深拷贝,改副本不会污染原对象引用
_.keys(obj) / _.values(obj)
取键名/键值数组

字符串 6

_.camelCase("Foo Bar")
转驼峰命名
_.kebabCase("FooBar")
转短横线命名
_.template("<%= name %>")({ name: "Vue" })
编译模板字符串
_.escape("<a>")
HTML 转义
_.trim(" abc ")
去除首尾空白
_.pad("abc", 8)
两端填充到指定长度

集合 6

_.groupBy(users, "age")
按 age 值把数组分组成 { age值: [元素...] } 的对象
_.sortBy(users, ["age", "name"])
按给定的属性序列排序,先比 age 再比 name
_.countBy([1, 2, 2, 3], Math.floor)
按规则统计频次,得到每个取值出现的次数
_.keyBy(users, "id")
以 id 为键把数组转成对象,方便 O(1) 取用
_.sampleSize(arr, 2)
不放回地随机抽取 2 个元素,抽样调研常用
_.shuffle(arr)
Fisher–Yates 洗牌,返回顺序被打乱的新数组

函数 6

_.debounce(fn, 300)
防抖(停止触发后才执行)
_.throttle(fn, 300)
节流(固定频率执行)
_.memoize(fn)
缓存函数结果
_.once(fn)
只执行一次
_.partial(fn, _, 2)
部分应用参数
_.bind(fn, thisArg, arg1)
绑定 this 与参数

数字与数学 5

_.random(0, 100)
生成 0-100 随机整数
_.range(0, 5)
生成 [0, 1, 2, 3, 4]
_.clamp(num, 0, 100)
限制在区间内
_.inRange(num, 0, 100)
判断是否在区间内
_.round(1.234, 2)
保留 2 位小数

实用判断 7

_.isEmpty(value)
判断是否为空
_.isEqual(a, b)
深度相等比较
_.isNil(value)
判断 null 或 undefined
_.isArray(value) / _.isObject(value)
类型判断
_.has(obj, "a.b")
判断是否有属性路径
_.defaults({ a: 1 }, { a: 0, b: 2 })
填充未定义属性
_.noop()
空函数占位

参数矩阵

参数作用示例
_.map(arr, fn)逐项变换收集为新数组,不改原数组_.map(arr, (n) => n * 2)
_.filter(arr, fn)保留回调返回真值的元素_.filter(arr, (n) => n > 0)
_.reduce(arr, fn, init)把数组逐项累计为一个值_.reduce(arr, (sum, n) => sum + n, 0)
_.chunk(arr, size)按固定大小切分数组,适合分批处理_.chunk(arr, 2)
_.flattenDeep(nested)把任意层嵌套一维化_.flattenDeep([1, [2, [3]]])
_.uniq(arr)去重并保留首次出现顺序_.uniq([1, 2, 1, 3])
_.union([a], [b])合并多数组并按唯一值去重_.union([1, 2], [2, 3])
_.get(obj, "a.b.c", def)安全读深层属性,缺失时回退默认值_.get(obj, "a.b.c", "default")
_.set(obj, "a.b.c", v)安全写深层属性,自动创建中间层_.set(obj, "a.b.c", value)
_.pick(obj, keys)只取出指定键组成新对象_.pick(obj, ["name", "age"])
_.omit(obj, keys)去掉指定键,常用于剔除敏感字段_.omit(obj, ["password"])
_.merge(target, source)深合并,嵌套对象按层合并而非整体覆盖_.merge(target, source)
_.cloneDeep(obj)递归深拷贝,修改副本不污染原引用_.cloneDeep(obj)
_.keys(obj) / _.values(obj)取键名数组 / 键值数组_.keys(obj) / _.values(obj)
_.camelCase(str)转驼峰命名_.camelCase("Foo Bar")
_.kebabCase(str)转短横线命名_.kebabCase("FooBar")
_.template(str)(data)编译模板字符串并填入数据_.template("<%= name %>")({ name: "Vue" })
_.escape(str)HTML 转义,防止内容被当标签解析_.escape("<a>")
_.trim(str) / _.pad(str, len)去首尾空白 / 两端填充到指定长度_.trim(" abc ")
_.groupBy(arr, "age")按属性值把数组分组成对象_.groupBy(users, "age")
_.sortBy(arr, ["age", "name"])按多属性序列依次比较排序_.sortBy(users, ["age", "name"])
_.countBy(arr, fn)按规则统计各取值出现频次_.countBy([1, 2, 2, 3], Math.floor)
_.keyBy(arr, "id")以指定属性为键把数组转成对象,便于按键取用_.keyBy(users, "id")
_.sampleSize(arr, n)不放回随机抽取 n 个元素_.sampleSize(arr, 2)
_.shuffle(arr)Fisher–Yates 洗牌,返回新数组_.shuffle(arr)
_.debounce(fn, 300)防抖:停止触发后才执行一次_.debounce(fn, 300)
_.throttle(fn, 300)节流:固定频率内最多执行一次_.throttle(fn, 300)
_.memoize(fn)缓存函数结果,相同入参直接复用_.memoize(fn)
_.once(fn)包装成只执行一次的函数_.once(fn)
_.partial(fn, _, 2)部分应用,预置部分参数_.partial(fn, _, 2)
_.bind(fn, thisArg, arg1)绑定 this 与前置参数_.bind(fn, thisArg, arg1)
_.random(0, 100)生成区间内的随机整数_.random(0, 100)
_.range(0, 5)生成 [0, 1, 2, 3, 4] 这样的数组_.range(0, 5)
_.clamp(num, 0, 100)把数值限制在区间内_.clamp(num, 0, 100)
_.inRange(num, 0, 100)判断数值是否落在区间内_.inRange(num, 0, 100)
_.round(1.234, 2)四舍五入保留指定位数小数_.round(1.234, 2)
_.isEmpty(value)判断是否为空(空数组、空对象、空串等)_.isEmpty(value)
_.isEqual(a, b)深度相等比较_.isEqual(a, b)
_.isNil(value)判断是否为 null 或 undefined_.isNil(value)
_.isArray / _.isObject类型判断_.isArray(value) / _.isObject(value)
_.has(obj, "a.b")判断是否拥有该属性路径_.has(obj, "a.b")
_.defaults({ a: 1 }, { a: 0, b: 2 })为未定义的属性填充默认值_.defaults({ a: 1 }, { a: 0, b: 2 })
_.noop()空函数,用作占位默认值_.noop()

易错点与避坑指南

现象搜索框每敲一个字就发一次请求,输入快了直接把后端打满。

原因直接绑在 input 事件上做远程搜索,没有做频率控制。

处置改用 _.debounce(fn, 300):停止触发 300 毫秒后才真正发起一次请求,而不是每键都发。

现象滚动或 resize 时页面卡顿,掉帧明显。

原因在高频事件上执行了重计算或布局操作,且没有限制执行频率。

处置用 _.throttle(fn, 300) 把处理压到固定频率,节流适合这类持续触发而不是等待停止的场景。

现象用户输入的 < 标签被当成真实 HTML 渲染,页面结构被破坏。

原因把未转义的文本直接插入 HTML。

处置插入前用 _.escape(str) 做 HTML 转义,或改用 textContent 写入纯文本。

现象把 lodash 整包 import 进项目,产物体积多出几百 KB。

原因整包引入时打包器难以在所有场景下摇掉未使用的函数。

处置改用 lodash-es 的 ES Module 版本,或按需单函数引入:import cloneDeep from "lodash/cloneDeep",让打包器能摇掉其余代码。

现象_.merge 之后源对象里本该被整体替换的字段反而留在了目标对象里。

原因merge 是按层深合并,嵌套对象不会被整体覆盖,而是逐键递归合并。

处置确实要整体替换时先 _.cloneDeep 再赋值,或改用一次性覆盖语义的处理方式,不要依赖 merge 做替换。

现象_.isEmpty 判断空字符串或数字为 true,业务上的「有值」判断出错。

原因isEmpty 的语义是「自身没有可枚举属性」,对 0 这样的原始值也返回 true。

处置先 _.isNil(value) 排除 null/undefined,再用真假判断或 _.inRange 等针对类型的检查分别处理。

现象_.get(obj, "a.b.c") 明明数据里有值却总拿到默认值。

原因路径是字符串形式,若键名里本身含有点号(如 "a.b" 是一个整体键名),会被当成嵌套路径拆开。

处置改用数组形式路径传参,_.get(obj, ["a.b", "c"]) 明确键名边界,避免点号歧义。

排障路径

  1. 1安全读取深层属性并观察回退值

    _.get(obj, "a.b.c", "default")

    a 或 a.b 不存在时不会抛错,而是返回 "default",可直接确认断链位置。

  2. 2确认数组是否真的为空

    _.isEmpty(value)

    对数组、对象、空串都适用;对数字返回 true,需配合 _.isNil 判断。

  3. 3深拷贝后修改副本验证不污染原对象

    _.cloneDeep(obj)

    在副本上改嵌套字段,再打印原对象,若原对象不变说明深拷贝生效。

  4. 4查看去重与合并后的结果

    _.union([1, 2], [2, 3])

    输出 [1, 2, 3],可确认唯一值合并的语义符合预期。

  5. 5给高频事件加防抖

    _.debounce(fn, 300)

    把 fn 包一层赋给事件处理器,连续触发结束后只执行最后一次。

  6. 6按需引入单个方法控制体积

    import cloneDeep from "lodash/cloneDeep"

    只引入实际用到的那一个函数,其余代码会被打包器摇掉。

命令示例

debounce 防抖搜索 + throttle 节流滚动

const onSearch = _.debounce(function (q) { $.getJSON("/api/search?q=" + q, render) }, 300) const onScroll = _.throttle(function () { updateParallax() }, 100) $("#q").on("input", (e) => onSearch(e.target.value))

debounce 等你停止输入后才发请求,适合搜索与自动保存;throttle 固定频率触发,适合滚动、resize 这类持续事件。注意默认配置下首次调用会延后到静默期结束。

omit 剔除敏感字段 + get 读取深层配置

const safe = _.omit(user, ["password", "token"]) const timeout = _.get(config, "http.timeout", 3000) _.set(config, "http.timeout", 5000)

把白名单式序列化(omit)放在统一出口,比在每个接口手写字段列表更不容易漏;get 的默认值只在解析出 undefined 时生效,显式存的 null 会原样返回。

groupBy 分组 + keyBy 建索引 + sortBy 多字段排序

const byAge = _.groupBy(users, "age") const byId = _.keyBy(users, "id") const sorted = _.sortBy(users, ["age", "name"]) const counts = _.countBy([1, 2, 2, 3], Math.floor)

在循环里反复 users.find(u => u.id === id) 是 O(n) 扫描,keyBy 一次转成对象后都是 O(1);sortBy 的数组形式表示按顺序逐级比较,是最常被漏用的签名。

memoize 缓存昂贵计算 + once 保护单次初始化

const expensive = _.memoize(function (key) { return heavyLookup(key) }) const init = _.once(function () { client.start() })

memoize 以第一个参数为缓存键,对纯函数最安全;传入对象参数会因引用不同而缓存不命中。once 适合模块顶层的初始化,防止被多次 require 或多次点击重复执行。

数据清洗链路:chunk + flattenDeep + uniq

const ids = _.uniq(_.flattenDeep(raw)); const pages = _.chunk(ids, 100); for (const page of pages) { upload(page) }

外部接口常返回带 null 与嵌套结构的混合数组,flattenDeep 拉平后 uniq 去掉重复 id,再按固定大小 chunk 分批提交,避免一次性请求体过大被网关拒绝。

常见坑与注意事项

  • _.merge(target, source) 会原地改写第一个参数并返回它,多个请求共用的配置对象会被悄悄写脏;先 _.cloneDeep 再 merge。
  • _.uniq / _.union 只做 SameValueZero 浅比较,对象数组里的重复对象去不掉;需要对内容去重时改用 _.uniqWith(arr, _.isEqual)。
  • _.get(obj, "a.b", 默认值) 只在解析结果为 undefined 时回退默认值:显式存着 null 时返回的是 null 而非默认值,下游按默认值做的判断会全部失效。
  • _.template 的 <%= %> 不做 HTML 转义,拼入用户输入即成 XSS;需要输出原始 HTML 时才用 <%- %>,可读文本一律用 <%= %>。
  • _.memoize 没有缓存上限且以参数值为键,对每条记录都调用的场景会把整份数据塞进缓存;长生命周期页面上表现为内存只涨不落。
  • _.debounce / _.throttle 返回的是新函数,每次绑定都产生一个新实例:事件绑定写 $(el).on("click", _.debounce(fn, 300)) 会在每次绑定时叠加防抖,实际退化成节流甚至多次执行。

提示

  • lodash-es 是 ES Module 版本,支持 Tree Shaking,打包体积更小。
  • _.get 和 _.set 可以安全操作深层嵌套对象,避免 undefined 报错。
  • debounce 适合搜索输入框,throttle 适合滚动/resize 事件。
  • 现代 JS 可以用原生方法替代部分 Lodash,如 Array.prototype.flat()、Object.keys()。
  • 按需引入单个方法 import cloneDeep from "lodash/cloneDeep" 可进一步减小体积。

常见问题

防抖和节流在时间线上有什么区别?

_.debounce(fn, 300) 以「最后一次触发」为基准:连续触发期间不断重置计时器,只有停止 300 毫秒后才真正执行一次,适合搜索输入框——用户敲完关键词才去请求。_.throttle(fn, 300) 以固定频率执行:不管触发多密集,300 毫秒窗口内最多执行一次,窗口过后若仍有新触发就再执行一次,适合滚动和 resize 这类高频且需要连续反馈的事件。选错的典型症状是搜索框发一串请求(该用防抖却用了节流),或者滚动到底后列表还来不及加载(该用节流却用了防抖)。

深拷贝和深合并,什么时候必须用 Lodash 而不是展开运算符?

展开运算符 {...obj} 是浅拷贝,只复制一层引用:obj.nested 依然与原对象共享同一个引用,改副本的嵌套字段会污染原对象。_.cloneDeep(obj) 递归复制每一层,改副本完全不影响原对象,适合编辑器式「取消编辑就回滚」的场景。_.merge(target, source) 则是深合并,嵌套对象按层合并而不是整体覆盖——传 source 里只有部分字段时,未提及的字段会保留 target 的原值。危险写法是 {...defaults, ...user}:它对顶层有效,一旦有嵌套配置就会整块丢掉默认值,应改成从 defaults 出发的深合并。

把数组转成以 id 为键的对象,用哪个方法最合适?

_.keyBy(users, "id") 直接以指定属性作为键输出对象,得到形如 { "1": user1, "2": user2 } 的结构,后续取用是 O(1),适合「渲染前把列表转成字典反复按 id 查找」的场景。相近但语义不同:_.groupBy(users, "age") 按值分组,输出的是 { 25: [user...], 30: [user...] },同一个值会聚成数组,适合按年龄/分类分堆展示;_.pick(obj, ["name", "age"]) 是对单个对象取子集,得到白名单新对象,常用于剔除 password 这类敏感字段时配合 _.omit。

为什么推荐从 lodash-es 按需引入而不是整包导入?

整包 import _ from "lodash" 会把整个库纳入构建,即便你只用 debounce 和 groupBy。lodash-es 是 ES Module 版本,配合 Tree Shaking,打包器能识别出未使用的具名导出并剔除,配合按需单文件引入 import cloneDeep from "lodash/cloneDeep" 可以做到只打进实际调用的函数。反过来说,Tree Shaking 依赖静态的 import 语句,若把 _ 当成运行时对象做属性访问(如 map 下标遍历),摇树就会失效。因此引入方式要和用法一起设计:用具名导入,别把库对象存起来传。

嵌套对象取值和合并,_.get / _.set / _.has 是怎么配合的?

三者共用「属性路径字符串」这一套约定。_.get(obj, "a.b.c", "default") 在 a 或 a.b 缺失时直接返回默认值而不抛错,适合处理来路不明的响应体;_.set(obj, "a.b.c", value) 写值时会自动创建中间的 a 和 a.b 对象,省掉逐层判空;_.has(obj, "a.b") 则用于判断路径是否存在,常用作「字段必填」的守卫。路径支持数组写法(_.get(obj, ["a", "b"])),在键名本身含点号时必须用数组形式,避免被误拆成多层。注意 _.get 只是读取快照,源对象为空时它返回 undefined 而不会创建节点,要真正建结构得用 _.set。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们