Lodash源码阅读-differenceBy

Lodash 源码阅读-differenceBy

概述

differenceBy 是 Lodash 中的一个数组操作函数,它用于计算第一个数组与其他数组的差集,并且支持通过迭代器函数指定比较的条件。它与普通的 difference 函数的区别在于,它可以对每个元素应用转换函数后再进行比较,这使得它在处理复杂对象或需要特定比较条件的场景下特别有用。

前置学习

依赖函数

  • baseRest:处理剩余参数的基础函数,将参数转换为符合特定 API 要求的形式
  • last:获取数组的最后一个元素
  • isArrayLikeObject:检查值是否是类数组对象
  • baseDifference:计算差集的内部实现函数
  • baseFlatten:将嵌套数组扁平化的内部函数
  • getIteratee:获取迭代器函数的工具函数

技术知识

  • 函数式编程:柯里化、函数组合等概念
  • 参数处理技巧:处理可变参数和参数重载
  • 类型检查:JavaScript 中的类型检查方法
  • 数组操作:数组扁平化、差集计算等操作
  • 迭代器模式:如何使用迭代器转换数据

源码实现

js 复制代码
var differenceBy = baseRest(function (array, values) {
  var iteratee = last(values);
  if (isArrayLikeObject(iteratee)) {
    iteratee = undefined;
  }
  return isArrayLikeObject(array)
    ? baseDifference(
        array,
        baseFlatten(values, 1, isArrayLikeObject, true),
        getIteratee(iteratee, 2)
      )
    : [];
});

实现思路

differenceBy 函数的实现非常简洁优雅,主要分为以下几个步骤:

  1. 首先,通过 baseRest 包装原始函数,处理函数的可变参数
  2. 从参数中提取最后一个值作为潜在的迭代器函数
  3. 检查这个值是否为数组或类数组对象,如果是,则不将其视为迭代器函数
  4. 检查第一个参数(要处理的数组)是否为数组或类数组对象
  5. 如果是数组,则使用 baseDifference 计算差集,否则返回空数组
  6. 在计算差集时,将除第一个参数外的所有参数扁平化为一个数组
  7. 根据提供的迭代器函数(或默认迭代器)对比较元素进行转换后再计算差集

这种实现方式既保证了函数的灵活性(支持多种参数形式),又确保了函数的健壮性(各种边缘情况处理)。

源码解析

1. 函数包装处理

js 复制代码
var differenceBy = baseRest(function (array, values) {
  // 函数体
});

baseRest 函数在这里起到了关键作用,它将传统的函数参数列表转换为现代的 Rest 参数形式。具体来说:

  • array 参数接收第一个传入的参数(要处理的数组)
  • values 参数以数组形式接收剩余的所有参数(要排除的值和可能的迭代器)

这种处理使得 differenceBy 可以接受任意数量的参数,增强了函数的灵活性。例如:

js 复制代码
// 所有这些调用形式都是有效的
_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
_.differenceBy([2.1, 1.2], [2.3], [3.4], Math.floor);
_.differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], "x");

2. 迭代器提取

js 复制代码
var iteratee = last(values);
if (isArrayLikeObject(iteratee)) {
  iteratee = undefined;
}

这段代码从 values 数组中提取最后一个元素,作为潜在的迭代器函数。但有一个重要的检查:如果这个元素本身是数组或类数组对象,则不将其视为迭代器函数。

这是一个巧妙的设计,考虑以下情况:

  • 如果调用 _.differenceBy([1, 2], [2, 3], Math.floor)Math.floor 被提取为迭代器
  • 如果调用 _.differenceBy([1, 2], [2, 3], [4]),最后一个参数 [4] 是数组,所以不会被视为迭代器,而是被视为要排除的值之一

这种设计使得 API 更加直观,用户不需要特别区分迭代器参数的位置。

3. 数组类型检查

js 复制代码
return isArrayLikeObject(array)
  ? baseDifference(
      array,
      baseFlatten(values, 1, isArrayLikeObject, true),
      getIteratee(iteratee, 2)
    )
  : [];

这里先检查第一个参数 array 是否为数组或类数组对象。这是必要的安全检查,因为:

  • 如果 array 不是数组类型,计算差集没有意义
  • 返回空数组是一个安全的默认值,符合函数式编程的理念

例如,以下调用会直接返回空数组:

js 复制代码
_.differenceBy(null, [1, 2, 3], Math.floor); // => []
_.differenceBy("string", [1, 2, 3], Math.floor); // => []

4. 参数扁平化处理

js 复制代码
baseFlatten(values, 1, isArrayLikeObject, true);

baseFlatten 函数将 values 数组(包含所有排除值和可能的迭代器)扁平化,其参数含义:

  • values:要扁平化的数组
  • 1:扁平化深度,这里只扁平化一层
  • isArrayLikeObject:用于检查元素是否应该被扁平化的函数
  • true:是否移除数组中的非数组元素(迭代器函数)

这个扁平化处理允许用户以多种方式传递排除值:

js 复制代码
// 以下调用是等效的
_.differenceBy([1, 2], [2], [3], Math.floor);
_.differenceBy([1, 2], [[2], [3]], Math.floor);

5. 迭代器处理

js 复制代码
getIteratee(iteratee, 2);

getIteratee 函数将用户提供的迭代器转换为标准形式:

  • 如果 iterateeundefined(没有提供迭代器或最后一个参数是数组),则使用默认的 _.identity 迭代器
  • 如果 iteratee 是字符串,则转换为属性访问器函数
  • 如果 iteratee 是函数,则直接使用该函数
  • 参数 2 表示迭代器的参数个数上限

这种灵活性允许用户使用多种形式的迭代器:

js 复制代码
// 使用函数作为迭代器
_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);

// 使用字符串属性作为迭代器(简写形式)
_.differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], "x");

6. 差集计算

js 复制代码
baseDifference(
  array,
  baseFlatten(values, 1, isArrayLikeObject, true),
  getIteratee(iteratee, 2)
);

最后,baseDifference 函数执行实际的差集计算:

  • 第一个参数是原始数组
  • 第二个参数是扁平化后的排除值数组
  • 第三个参数是处理后的迭代器函数

baseDifference 会对每个元素应用迭代器函数,然后比较转换后的值,返回仅在第一个数组中存在的元素。

总结

differenceBy 函数是 Lodash 中灵活且强大的数组差集计算工具,其核心优势在于:

  1. 灵活的参数处理:可以接受任意数量的数组参数和一个可选的迭代器函数
  2. 多种迭代器形式:支持函数迭代器和字符串属性简写
  3. 健壮的边缘情况处理:安全处理非数组输入和特殊参数形式
  4. 高效的实现 :内部使用优化的 baseDifference 实现,确保性能
  5. 直观的 API 设计:迭代器位置的自动推断,减少使用障碍

这种设计展示了函数式编程中参数处理、类型检查和函数组合的最佳实践,是学习 JavaScript 函数式编程的优秀范例。

相关推荐
JarvanMo15 分钟前
借助FlutterFire CLI实现Flutter与Firebase的多环境配置
前端·flutter
Jedi Hongbin29 分钟前
echarts自定义图表--仪表盘
前端·javascript·echarts
凯哥197033 分钟前
Sciter.js指南 - 桌面GUI开发时使用第三方模块
前端
边洛洛33 分钟前
对Electron打包的exe文件进行反解析
前端·javascript·electron
财神爷亲闺女34 分钟前
js 实现pc端鼠标横向拖动滚动
前端
用户20311966009634 分钟前
sheet在SwiftUI中的基本用法
前端
晴殇i35 分钟前
一行代码搞定防抖节流:JavaScript新特性解析
前端·javascript
David凉宸38 分钟前
HTML表单(二)
前端