Lodash源码阅读-unzip

179 阅读4分钟

Lodash 源码阅读-unzip

概述

unzip函数用于将打包的数组解包,即将一个分组的数组(如[[1, 3], [2, 4]])转换为一个重组的数组(如[[1, 2], [3, 4]])。它实质上是将多个数组中对应位置的元素重新组合成新数组,可视为zip操作的逆向过程。该函数会忽略非数组类型的元素,并基于最长数组元素进行结果填充。

前置学习

依赖函数

  • arrayFilter: 过滤数组中的元素,返回符合条件的元素组成的新数组
  • isArrayLikeObject: 检查值是否为类数组对象
  • nativeMax: 获取两个数值中的最大值
  • baseTimes: 调用迭代函数 n 次,返回由迭代函数返回值组成的数组
  • arrayMap: 对数组中的每个元素执行迭代函数,返回新数组
  • baseProperty: 创建一个返回对象指定属性值的函数

技术知识

  • 数组操作与转换
  • 高阶函数的应用
  • 函数式编程思想
  • JavaScript 中的类数组对象概念
  • 矩阵转置操作原理

源码实现

function unzip(array) {
  if (!(array && array.length)) {
    return [];
  }
  var length = 0;
  array = arrayFilter(array, function (group) {
    if (isArrayLikeObject(group)) {
      length = nativeMax(group.length, length);
      return true;
    }
  });
  return baseTimes(length, function (index) {
    return arrayMap(array, baseProperty(index));
  });
}

实现思路

  • unzip首先检查输入是否为有效数组,
  • 然后过滤出所有类数组对象元素,同时计算出最长子数组的长度。
  • 接着,基于这个最大长度创建结果数组,对于每个索引位置,从所有子数组中提取相同索引位置的元素形成新数组。

本质上,这是一个矩阵转置操作,将"行优先"的数据结构转换为"列优先"的结构。

源码解析

1. 参数校验与初始化

if (!(array && array.length)) {
  return [];
}
var length = 0;

这段代码首先检查传入的array参数是否为真值且具有length属性。如果参数无效(例如为nullundefined或空数组),直接返回空数组。然后初始化length变量用于后续记录最长子数组的长度。

2. 数据预处理与有效性过滤

array = arrayFilter(array, function (group) {
  if (isArrayLikeObject(group)) {
    length = nativeMax(group.length, length);
    return true;
  }
});

使用arrayFilter过滤原数组,只保留类数组对象元素。对于每个符合条件的元素,通过nativeMax更新length变量,确保它始终等于所有子数组中的最大长度。这样处理确保了即使子数组长度不一,也能基于最长的子数组创建结果。

例如,对于输入[[1, 2, 3], [4, 5], 'not-an-array']

  • 第一个元素[1, 2, 3]是类数组对象,保留,length更新为 3
  • 第二个元素[4, 5]是类数组对象,保留,length保持为 3
  • 第三个元素'not-an-array'不是类数组对象,被过滤掉
  • 过滤后的数组为[[1, 2, 3], [4, 5]]length为 3

3. 结果生成与矩阵转置

return baseTimes(length, function (index) {
  return arrayMap(array, baseProperty(index));
});

这是实现矩阵转置的核心逻辑。baseTimes创建一个长度为length的新数组,对于每个索引位置index

  • 使用baseProperty(index)创建一个函数,该函数返回对象的第index个属性值
  • 使用arrayMap对过滤后的数组中的每个子数组应用这个函数,提取第index个元素
  • 最终结果是一个新数组,其中每个元素都是原数组中相同位置的元素集合

例如,对于过滤后的数组[[1, 2, 3], [4, 5]]length=3

  • 索引 0:提取每个子数组的第 0 个元素,得到[1, 4]
  • 索引 1:提取每个子数组的第 1 个元素,得到[2, 5]
  • 索引 2:提取每个子数组的第 2 个元素,得到[3, undefined](注意第二个子数组没有第 2 个元素)
  • 最终结果为[[1, 4], [2, 5], [3, undefined]]

总结

  • 巧用nativeMax动态记录最大长度,解决了输入数组不等长的问题
  • 通过basePropertyarrayMap组合实现了高效的矩阵转置操作
  • 采用函数式编程思想,将复杂操作分解为独立且可组合的函数
  • 使用arrayFilter确保只处理有效输入,提高了代码的健壮性