Lodash 源码阅读-baseXor
概述
baseXor 是 Lodash 内部的基础函数,用于实现数组的异或操作(XOR)。该函数接收多个数组作为输入,返回仅在其中一个数组中出现的元素集合,并支持自定义比较器和迭代器。它是 _.xor、_.xorBy 和 _.xorWith 等公共方法的核心实现。
前置学习
依赖函数
- baseDifference:计算两个数组之间的差集,支持自定义比较器和迭代器
- baseUniq:创建数组的去重版本,支持自定义比较函数
- baseFlatten:将多维数组扁平化处理为指定深度的一维数组
- arrayFilter:根据指定条件过滤数组元素
技术知识
- 异或操作:集合的对称差概念,返回仅在一个集合中存在的元素
- 数组操作:数组遍历技术与差集计算
- 高阶函数:迭代器和比较器函数的应用
- 函数式编程:组合多个基础函数实现复杂逻辑
源码实现
function baseXor(arrays, iteratee, comparator) {
var length = arrays.length;
if (length < 2) {
return length ? baseUniq(arrays[0]) : [];
}
var index = -1,
result = Array(length);
while (++index < length) {
var array = arrays[index],
othIndex = -1;
while (++othIndex < length) {
if (othIndex != index) {
result[index] = baseDifference(
result[index] || array,
arrays[othIndex],
iteratee,
comparator
);
}
}
}
return baseUniq(baseFlatten(result, 1), iteratee, comparator);
}
实现思路
baseXor 实现多数组异或操作的核心思路是通过差集运算。首先判断输入数组数量,对每个数组与其他所有数组进行差集运算,获取只在当前数组中存在的元素。然后将所有结果扁平化并去重,得到最终的异或结果。这种实现方式能够处理任意数量的输入数组,并通过迭代器和比较器提供自定义比较逻辑。
源码解析
1. 参数处理与边界检查
function baseXor(arrays, iteratee, comparator) {
var length = arrays.length;
if (length < 2) {
return length ? baseUniq(arrays[0]) : [];
}
函数首先检查传入的数组数量。如果只有一个数组,则直接返回该数组的去重结果;如果没有数组,则返回空数组。这是对特殊情况的处理,确保函数行为的一致性。
2. 初始化结果数组
var index = -1,
result = Array(length);
创建与输入数组数量相同长度的结果数组,用于存储中间计算结果。通过预分配数组大小,避免了动态扩容的性能开销。
3. 异或核心计算逻辑
while (++index < length) {
var array = arrays[index],
othIndex = -1;
while (++othIndex < length) {
if (othIndex != index) {
result[index] = baseDifference(
result[index] || array,
arrays[othIndex],
iteratee,
comparator
);
}
}
}
这个嵌套循环是整个异或操作的核心:
- 外层循环遍历每个输入数组
- 内层循环与其他所有数组计算差集
othIndex != index确保不与自身比较result[index] || array在首次计算时使用原数组,后续使用累积的差集结果baseDifference支持通过iteratee和comparator自定义比较方式
表达式result[index] || array的工作原理是这样的:
在计算过程中,我们需要把每个数组与其他所有数组进行差集计算。对于每个数组:
-
首次计算时:
result[index]还是undefined(因为刚开始结果数组是空的)- 此时
undefined || array会返回原始数组array - 所以第一次计算会用原始数组作为基础
- 此时
-
后续计算时:
result[index]已经存储了上一次计算的差集结果- 比如与第一个数组计算完差集后,结果存入
result[index] - 再与第二个数组计算时,就用上一步的结果而不是原始数组了
- 比如与第一个数组计算完差集后,结果存入
举个例子,假设有三个数组[1,2], [2,3], [3,4]:
- 处理第一个数组时:
- 与第二个数组计算差集:
[1,2] - [2,3] = [1](去掉共有元素 2) - 将
[1]存入result[0] - 与第三个数组计算差集:
[1] - [3,4] = [1](没有共有元素) - 最终
result[0]是[1]
- 与第二个数组计算差集:
这样通过多次迭代,每个数组都只保留了其独有的元素。
例如,对于输入 [[1,2], [2,3], [1,3]]:
- 第一个数组
[1,2]经过处理后,只保留了[](都被其他数组包含) - 第二个数组
[2,3]经过处理后,只保留了[] - 第三个数组
[1,3]经过处理后,只保留了[]
4. 结果处理与返回
return baseUniq(baseFlatten(result, 1), iteratee, comparator);
}
最后将结果数组扁平化为一维数组,并使用相同的迭代器和比较器进行去重,得到最终的异或结果。baseFlatten 的参数 1 表示只扁平化一层,确保得到正确的一维结果数组。
总结
baseXor 函数通过巧妙的差集计算实现了数组的异或操作,其实现体现了以下几个关键技术点:
- 利用多次差集计算模拟异或操作,优化了多数组情况下的处理策略
- 通过参数预分配和条件判断优化算法性能
- 支持自定义比较逻辑,提高了函数的适用范围和灵活性
- 复用基础函数实现复杂逻辑,体现了良好的模块化设计思想
现代 JavaScript 中可使用 Set 实现类似功能,但 Lodash 的实现提供了更丰富的自定义能力和边界处理。