Lodash 源码阅读-xorBy
概述
xorBy 是 Lodash 中的一个数组操作函数,它类似于 xor,但允许通过迭代器函数自定义比较标准。这个函数计算多个数组的异或结果,通过对每个元素应用迭代器函数生成比较值,然后基于这些值进行异或操作。它返回仅在一个数组中出现的元素集合,增强了数据比较的灵活性和精度。
前置学习
依赖函数
- baseXor:内部实现异或操作的核心函数,执行多个数组间的对称差集计算
- baseRest:处理函数的剩余参数,实现类似 ES6 的 rest 参数功能
- last:获取数组的最后一个元素,用于提取迭代器函数参数
- arrayFilter:过滤数组元素,确保只有有效的数组参与计算
- isArrayLikeObject:检查值是否类似数组对象,用于参数验证
- getIteratee:获取迭代器函数,支持多种迭代器形式(函数、字符串、对象等)
技术知识
- 高阶函数:函数作为参数传递与处理的编程模式
- 迭代器模式:使用迭代器函数处理集合元素的技术
- 参数处理:处理可变参数和尾部特殊参数的方法
- 集合操作:集合的异或(对称差)计算原理
- 函数式编程:纯函数和函数组合等函数式编程概念
源码实现
var xorBy = baseRest(function (arrays) {
var iteratee = last(arrays);
if (isArrayLikeObject(iteratee)) {
iteratee = undefined;
}
return baseXor(
arrayFilter(arrays, isArrayLikeObject),
getIteratee(iteratee, 2)
);
});
实现思路
xorBy 函数的核心思路如下:
- 接收多个数组参数,并可能在最后一个参数位置提供迭代器函数
- 判断最后一个参数是否为迭代器函数(不是类数组对象则视为迭代器)
- 过滤掉非类数组对象的参数,确保只有有效数组参与计算
- 使用
baseXor计算异或结果,同时应用迭代器函数处理每个元素
这种设计既支持灵活的参数传递方式,又通过迭代器函数支持自定义比较逻辑,使得函数能够处理复杂的数据比较场景。
源码解析
1. 函数定义与参数收集
var xorBy = baseRest(function(arrays) {
首先使用 baseRest 创建函数,它可以接收任意数量的参数并将它们收集到 arrays 数组中。这是一种模拟 ES6 rest 参数的实现方式,使得函数能够处理可变数量的输入数组。
例如,当用户调用 _.xorBy([1, 2], [2, 3], [3, 4], Math.floor) 时,所有参数都会被收集到 arrays 中,形成 [[1, 2], [2, 3], [3, 4], Math.floor]。
2. 迭代器函数提取与验证
var iteratee = last(arrays);
if (isArrayLikeObject(iteratee)) {
iteratee = undefined;
}
这段代码获取 arrays 的最后一个元素,并检查它是否为类数组对象。如果是类数组对象,说明用户没有提供迭代器函数,将 iteratee 设置为 undefined;如果不是类数组对象,则认为它是迭代器函数。
这种设计允许两种调用形式:
// 使用自定义迭代器函数
_.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
// 使用属性路径作为迭代器
_.xorBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], "x");
在第一个例子中,Math.floor 被识别为迭代器函数;在第二个例子中,字符串 'x' 被识别为迭代器短语,后续会被转换为属性访问函数。
3. 参数过滤与异或计算
return baseXor(
arrayFilter(arrays, isArrayLikeObject),
getIteratee(iteratee, 2)
);
最后一步完成了两个关键操作:
- 使用
arrayFilter过滤arrays,只保留类数组对象,确保异或计算的输入有效 - 使用
getIteratee处理迭代器函数,并将其与过滤后的数组一起传递给baseXor进行计算
getIteratee(iteratee, 2) 这个调用非常重要,它处理多种形式的迭代器:
- 如果
iteratee是函数,直接返回该函数 - 如果
iteratee是字符串,返回一个属性访问函数(如_.property('x')) - 如果
iteratee是对象,返回一个匹配函数(如_.matches({ 'x': 1 })) - 如果
iteratee为undefined,返回 Lodash 的identity函数
参数 2 表示返回的迭代器函数应接收两个参数,虽然在实际使用中迭代器通常只使用第一个参数(当前元素)。
4. 实际执行流程示例
当调用 _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor) 时,执行流程如下:
arrays被初始化为[[2.1, 1.2], [2.3, 3.4], Math.floor]iteratee被设置为Math.floor(最后一个元素)- 由于
Math.floor不是类数组对象,所以iteratee保持不变 arrayFilter过滤arrays,得到[[2.1, 1.2], [2.3, 3.4]]getIteratee(Math.floor, 2)返回Math.floor函数baseXor对过滤后的数组计算异或,使用Math.floor作为比较标准- 最终结果为
[1.2, 3.4],因为Math.floor(2.1) === Math.floor(2.3),它们被视为相同元素
总结
xorBy 函数通过引入迭代器机制,显著增强了 xor 的功能,使其能够满足更复杂的数据比较需求。其设计优势包括:
- 灵活的比较方式:通过迭代器函数支持自定义元素比较标准,适用于复杂数据结构
- 简洁统一的接口:保持与其他 Lodash 函数一致的调用方式,降低学习成本
- 健壮的参数处理:严格的参数验证和过滤,确保函数行为稳定可预测
- 强大的迭代器支持:支持多种形式的迭代器表达,增强了函数的表达能力
这个函数特别适合处理需要按特定标准比较的数据集,例如忽略对象中特定属性进行比较、对数值进行舍入比较等场景,是 Lodash 中优雅设计和函数式编程思想的典型代表。