Lodash源码阅读-flatRest

Lodash 源码阅读-flatRest

概述

flatRest 是 Lodash 内部的一个工具函数,它用于处理函数的剩余参数,并将剩余参数数组扁平化。这个函数在 Lodash 中广泛用于实现那些需要接收任意数量参数的 API,比如 _.pick_.omit 等。

前置学习

依赖函数

  • overRest: 用于处理函数的剩余参数,模拟 ES6 的 rest 参数功能
  • setToString : 保持函数的 toString 方法,用于调试和检查
  • flatten: 将嵌套数组扁平化一层

技术知识

  • 函数式编程: 高阶函数、函数组合
  • 剩余参数 : ES6 的 ...args 语法在 ES5 环境中的模拟实现
  • 函数元数据: 保持函数的字符串表示的重要性
  • 数组扁平化: 处理嵌套数组结构

源码实现

js 复制代码
function flatRest(func) {
  return setToString(overRest(func, undefined, flatten), func + "");
}

实现思路

flatRest 函数的实现看似简单,但背后包含了几个关键步骤:

  1. 使用 overRest 创建一个新函数,该函数能够将超出指定数量的参数收集到一个数组中
  2. 将这个参数数组通过 flatten 函数扁平化一层
  3. 使用 setToString 保持原函数的字符串表示,便于调试
  4. 返回这个增强后的新函数

这种实现方式使得 Lodash 的许多函数可以既接受参数列表,也接受数组参数,提高了 API 的灵活性。

源码解析

让我们对 flatRest 函数的实现进行更加深入的分析:

js 复制代码
function flatRest(func) {
  return setToString(overRest(func, undefined, flatten), func + "");
}

这个简短的函数实现了多层函数组合,我们从内到外逐层解析:

1. overRest 函数解析

js 复制代码
overRest(func, undefined, flatten);

overRest 是 Lodash 内部实现的一个模拟 ES6 剩余参数(rest parameters)的函数。

  • 作用:创建一个新函数,该函数会把超出特定数量的参数收集到一个数组中,然后对这个数组应用变换函数
  • 参数
    • func:原始函数
    • undefined:参数起始位置,这里传入 undefined 表示使用默认值,即 func.length - 1(原函数参数数量减 1)
    • flatten:应用于收集到的参数数组的变换函数

举个例子,假设我们有这样一个函数:

js 复制代码
function exampleFunc(a, b) {
  // 原始函数有2个命名参数
  console.log(a, b, arguments[2], arguments[3]);
}

// 应用 overRest 后
var transformed = overRest(exampleFunc, undefined, flatten);

当调用 transformed(1, 2, [3, 4], 5) 时,内部处理过程大致如下:

  1. 确定参数收集点:func.length - 1 = 2 - 1 = 1,即从第 2 个参数开始收集
  2. 前 1 个参数 1 直接传给原函数,从第 2 个参数开始(2, [3, 4], 5)被收集成数组 [2, [3, 4], 5]
  3. 对收集的数组应用 flatten 函数,得到 [2, 3, 4, 5]
  4. 最终调用 exampleFunc(1, [2, 3, 4, 5])

2. flatten 函数解析

flatten 函数负责将数组扁平化一层,处理嵌套数组的情况。

举例说明扁平化过程:

  • flatten([1, [2, 3], 4])[1, 2, 3, 4]
  • flatten([1, [2, [3, 4]], 5])[1, 2, [3, 4], 5](只扁平化一层)

这种扁平化处理确保了无论用户传入参数列表还是数组,最终处理的结果都是一致的。

3. setToString 函数解析

js 复制代码
setToString(newFunc, func + "");

setToString 函数用于保持函数的字符串表示,这看似是一个小细节,但在调试和函数检查时非常重要。

  • 原理 :在 JavaScript 中,Function.prototype.toString() 会返回函数的源代码文本
  • 问题:当我们用函数包装另一个函数时,原始函数的名称和结构信息会丢失
  • 解决方案setToString 通过修改函数的 toString 方法,使其返回原始函数的字符串表示

例如,没有 setToString 时:

js 复制代码
console.log(overRest(func, undefined, flatten).toString());
// 输出:可能是 "function anonymous() { ... }" 这样的形式

使用 setToString 后:

js 复制代码
console.log(
  setToString(overRest(func, undefined, flatten), func + "").toString()
);
// 输出:与原始 func 函数的 toString() 结果相同

4. 函数组合的数据流

整个 flatRest 函数体现了优雅的函数组合(function composition)模式,数据流向如下:

  1. 原始函数 func 作为输入
  2. 通过 overRest 转换为一个能处理剩余参数的新函数
  3. 在这个新函数中,剩余参数会通过 flatten 进行扁平化
  4. 通过 setToString 保持函数的字符串表示
  5. 返回最终的增强函数

这种嵌套调用方式是函数式编程的典型特征,每个函数负责一个明确的任务,然后通过组合实现更复杂的功能。

5. 性能考量

flatRest 的实现也考虑了性能因素:

  • 只扁平化一层数组,避免了深度递归扁平化可能带来的性能问题
  • 使用 overRest 而非直接使用 ES6 的 rest 参数,保证了在旧环境中的兼容性
  • 整个函数是一次性组合完成的,不需要多次遍历参数数组

应用示例

假设我们有一个使用 flatRestpick 函数:

js 复制代码
var pick = flatRest(function (object, paths) {
  // 假设 basePick 是内部实现
  return object == null ? {} : basePick(object, paths);
});

当用户这样调用时:

js 复制代码
// 调用方式 1
pick({ a: 1, b: 2, c: 3 }, "a", "c");

// 调用方式 2
pick({ a: 1, b: 2, c: 3 }, ["a", "c"]);

// 调用方式 3(混合方式)
pick({ a: 1, b: 2, c: 3 }, "a", ["b", "c"]);

内部处理过程:

  1. 调用方式 1

    • 参数:{a: 1, b: 2, c: 3}, 'a', 'c'
    • overRest 收集:第一个参数 {a: 1, b: 2, c: 3} 直接传递,其余参数 ['a', 'c'] 收集
    • flatten 处理:['a', 'c'] 已经是扁平的,不变
    • 最终调用:basePick({a: 1, b: 2, c: 3}, ['a', 'c'])
  2. 调用方式 2

    • 参数:{a: 1, b: 2, c: 3}, ['a', 'c']
    • overRest 收集:第一个参数 {a: 1, b: 2, c: 3} 直接传递,其余参数 [['a', 'c']] 收集(注意这是个嵌套数组)
    • flatten 处理:[['a', 'c']] 扁平化为 ['a', 'c']
    • 最终调用:basePick({a: 1, b: 2, c: 3}, ['a', 'c'])
  3. 调用方式 3

    • 参数:{a: 1, b: 2, c: 3}, 'a', ['b', 'c']
    • overRest 收集:第一个参数 {a: 1, b: 2, c: 3} 直接传递,其余参数 ['a', ['b', 'c']] 收集
    • flatten 处理:['a', ['b', 'c']] 扁平化为 ['a', 'b', 'c']
    • 最终调用:basePick({a: 1, b: 2, c: 3}, ['a', 'b', 'c'])

通过这样的处理,无论用户采用何种参数传递方式,最终都能得到一致的结果。

种调用方式都会得到相同的结果:{a: 1, c: 3}

总结

flatRest 函数是 Lodash 内部用于增强 API 灵活性的重要工具。它通过组合 overRestflattensetToString 三个函数,实现了以下功能:

  1. 收集超出函数定义的参数
  2. 扁平化收集到的参数数组
  3. 保持函数的原始字符串表示

这样一来,Lodash 的许多函数可以同时支持传入参数列表和数组,提高了 API 的易用性。在自己设计函数库或 API 时,我们也可以借鉴这种设计模式,让函数接口更加灵活友好。

相关推荐
进取星辰6 分钟前
28、动画魔法圣典:Framer Motion 时空奥义全解——React 19 交互动效
前端·react.js·交互
不爱吃饭爱吃菜1 小时前
uniapp微信小程序-长按按钮百度语音识别回显文字
前端·javascript·vue.js·百度·微信小程序·uni-app·语音识别
程序员拂雨2 小时前
Angular 知识框架
前端·javascript·angular.js
GoodStudyAndDayDayUp2 小时前
gitlab+portainer 实现Ruoyi Vue前端CI/CD
前端·vue.js·gitlab
程序员阿明2 小时前
vite运行只能访问localhost解决办法
前端·vue
前端 贾公子2 小时前
uniapp -- 验证码倒计时按钮组件
前端·vue.js·uni-app
zhengddzz2 小时前
从卡顿到丝滑:JavaScript性能优化实战秘籍
开发语言·javascript·性能优化
淡笑沐白2 小时前
AJAX技术全解析:从基础到最佳实践
前端·ajax
Go_going_2 小时前
ajax,Promise 和 fetch
javascript·ajax·okhttp
龙正哲3 小时前
如何在Firefox火狐浏览器里-安装梦精灵AI提示词管理工具
前端·firefox