Lodash源码阅读-chunk

Lodash 源码阅读-chunk

功能概述

chunk 函数是 Lodash 库中的一个实用工具函数,用于将数组拆分成多个长度为指定大小的小数组。如果原数组不能被平均分割,则最后一个小数组将包含剩余的元素。

前置学习

依赖函数

在理解 chunk 函数之前,建议先了解它所依赖的以下函数:

  • baseSlice :Lodash 内部函数,用于创建数组的切片,是 chunk 实现的核心
  • isIterateeCall:内部函数,用于检测参数是否为迭代回调
  • toInteger:将值转换为整数
  • nativeMax :原生 Math.max 函数的引用
  • nativeCeil :原生 Math.ceil 函数的引用

技术知识

  • 数组切片:理解如何从数组中提取部分元素
  • 向上取整 :使用 Math.ceil 计算需要创建的数组数量
  • 参数处理:处理可选参数和默认值
  • 数组遍历:使用循环构建结果数组

源码实现

javascript 复制代码
/**
 * Creates an array of elements split into groups the length of `size`.
 * If `array` can't be split evenly, the final chunk will be the remaining
 * elements.
 *
 * @static
 * @memberOf _
 * @since 3.0.0
 * @category Array
 * @param {Array} array The array to process.
 * @param {number} [size=1] The length of each chunk
 * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
 * @returns {Array} Returns the new array of chunks.
 * @example
 *
 * _.chunk(['a', 'b', 'c', 'd'], 2);
 * // => [['a', 'b'], ['c', 'd']]
 *
 * _.chunk(['a', 'b', 'c', 'd'], 3);
 * // => [['a', 'b', 'c'], ['d']]
 */
function chunk(array, size, guard) {
  if (guard ? isIterateeCall(array, size, guard) : size === undefined) {
    size = 1;
  } else {
    size = nativeMax(toInteger(size), 0);
  }
  var length = array == null ? 0 : array.length;
  if (!length || size < 1) {
    return [];
  }
  var index = 0,
    resIndex = 0,
    result = Array(nativeCeil(length / size));

  while (index < length) {
    result[resIndex++] = baseSlice(array, index, (index += size));
  }
  return result;
}

源码解析

1. 参数处理

javascript 复制代码
if (guard ? isIterateeCall(array, size, guard) : size === undefined) {
  size = 1;
} else {
  size = nativeMax(toInteger(size), 0);
}

这段代码处理 size 参数:

  • 如果提供了 guard 参数且 isIterateeCall(array, size, guard) 为真,或者 size 未定义,则将 size 设为默认值 1
  • 否则,将 size 转换为整数,并确保它不小于 0(使用 nativeMaxtoInteger(size) 和 0 的最大值)

guard 参数是一个内部参数,用于支持将 chunk 作为迭代函数用于 _.map 等方法。isIterateeCall 检查参数是否为迭代回调的一部分,这是 Lodash 内部的优化机制。

guard 参数和 isIterateeCall 函数详解

guard 参数和 isIterateeCall 函数是 Lodash 内部的一种机制,用于处理函数作为迭代器回调时的参数解析。这可能是 Lodash 中较为晦涩的部分,让我们详细解释:

  1. guard 参数的作用

    • guard 参数不是给普通用户使用的,而是 Lodash 内部使用的一个标记参数
    • 它的存在使得 Lodash 函数可以被用作其他高阶函数(如 _.map_.forEach 等)的回调函数
    • 注意源码中的 @param- 标记(带有破折号),这表明该参数不是公开文档的一部分
  2. isIterateeCall 函数的作用

    • 它检查当前函数调用是否是作为迭代回调(iteratee)的一部分
    • 具体来说,它会判断传入的参数是否符合作为迭代回调时的特定模式
    • 这样可以根据调用上下文来调整函数的行为
  3. 实际应用场景

    chunk 函数被用作迭代回调时,它可能会收到额外的参数。例如:

    javascript 复制代码
    _.map(
      [
        [1, 2, 3],
        [4, 5, 6],
      ],
      _.chunk
    );

    在这种情况下,chunk 会被这样调用:chunk(value, index, collection),其中:

    • value 是当前元素,例如 [1, 2, 3]
    • index 是当前索引,例如 0
    • collection 是整个集合

    没有 guard 参数和 isIterateeCall 检查,chunk 会将 index(一个数字)解释为 size 参数,这不是我们想要的行为。

2. 边界检查

javascript 复制代码
var length = array == null ? 0 : array.length;
if (!length || size < 1) {
  return [];
}

这部分进行边界检查:

  • 获取数组长度,如果数组为 nullundefined,则长度为 0
  • 如果数组长度为 0 或 size 小于 1,则直接返回空数组

这种防御性编程确保了函数能够安全地处理各种边缘情况,避免运行时错误。

3. 结果数组初始化

javascript 复制代码
var index = 0,
  resIndex = 0,
  result = Array(nativeCeil(length / size));

初始化结果数组:

  • index 用于跟踪原数组的当前位置
  • resIndex 用于跟踪结果数组的当前位置
  • result 是预先分配好大小的结果数组,大小为 Math.ceil(length / size)

使用 Math.ceil 向上取整确保了即使最后一个小数组不满 size 个元素,也能为其分配空间。预先分配数组大小比动态增长数组更高效,避免了多次内存重新分配。

4. 数组分块实现

javascript 复制代码
while (index < length) {
  result[resIndex++] = baseSlice(array, index, (index += size));
}

这是函数的核心逻辑,使用循环将原数组分成多个小数组:

  • 循环继续,直到处理完原数组中的所有元素
  • 每次迭代,使用 baseSlice 从原数组中提取从 indexindex + size 的元素(不包括 index + size
  • index += sizeindex 增加 size,同时作为 baseSlice 的结束索引
  • 将提取的小数组存储在结果数组的 resIndex 位置,并将 resIndex 增加 1

baseSlice 函数是 Lodash 内部的工具函数,用于创建数组的切片,类似于原生的 Array.prototype.slice,但有更好的性能和更一致的行为。

执行流程示例

_.chunk(['a', 'b', 'c', 'd', 'e'], 2) 为例,执行流程如下:

  1. 参数处理:size = 2
  2. 边界检查:数组长度为 5,size 为 2,继续执行
  3. 初始化:
    • index = 0
    • resIndex = 0
    • result = Array(Math.ceil(5/2)) = Array(3) (预分配长度为 3 的数组)
  4. 第一次循环:
    • result[0] = baseSlice(['a', 'b', 'c', 'd', 'e'], 0, 2)
    • result[0] = ['a', 'b']
    • index = 2
    • resIndex = 1
  5. 第二次循环:
    • result[1] = baseSlice(['a', 'b', 'c', 'd', 'e'], 2, 4)
    • result[1] = ['c', 'd']
    • index = 4
    • resIndex = 2
  6. 第三次循环:
    • result[2] = baseSlice(['a', 'b', 'c', 'd', 'e'], 4, 6)
    • result[2] = ['e'] (最后一个块包含剩余元素)
    • index = 6
    • resIndex = 3
  7. index > length,循环结束,返回 result = [['a', 'b'], ['c', 'd'], ['e']]

总结

chunk 函数是 Lodash 中一个简单但非常实用的工具函数,它通过将数组分割成指定大小的小数组,解决了许多实际开发中的常见问题。其设计体现了几个重要的软件工程原则:

  1. 简单性

    • 函数接口简洁明了,只需要两个主要参数:数组和大小
    • 实现逻辑直观,易于理解和维护
  2. 健壮性

    • 处理各种边缘情况,如空数组、无效的 size
    • 使用防御性编程风格,避免运行时错误
  3. 效率

    • 预先分配结果数组的大小,避免动态增长带来的性能开销
    • 使用内部优化的 baseSlice 函数,而不是原生的 Array.prototype.slice
  4. 灵活性

    • 支持作为迭代函数用于其他 Lodash 方法
    • 处理各种类型的输入,包括非数组值和非数字的 size
相关推荐
Moment5 分钟前
自己写的网站如何统计访问数据?一套轻量方案搞定 PV、UV 和停留时间
前端·javascript·面试
Dignity_呱13 分钟前
决定我的offer:问了我3个websocket的问题
前端·websocket·面试
五月仲夏27 分钟前
使用FormData格式上传图片
前端·vue
世界哪有真情28 分钟前
一分钟学会windows上使用Docker
前端·后端
掘金安东尼1 小时前
🧭 前端周刊第409期(2025年4月7日-13日)
前端·javascript·面试
凌览1 小时前
密码太多记不住?用Trae开发一个密码管理插件
前端·后端·trae
墨渊君1 小时前
React Native 与 React(Web) 开发的不同点, 如何快速上手?
前端·javascript·react native
林太白1 小时前
企业级NestJS如何创建项目学起来
前端·vue.js·后端
橙某人1 小时前
横向图片选择器之自动滚动定位功能-Javascript、Vue
前端·javascript·vue.js
无奈何杨1 小时前
策略规则指标字段引用关系与快捷跳转
前端·后端