lodash 源码解析 -- chunk

1,243 阅读2分钟

前言

lodash 中的 chunk 函数是一个比较实用的适用于数组工具函数,作用是将创建一个新数组,将传入的数组根据 size 参数的大小进行切片分组。如果最后数组无法整除的,就将剩余元素都放到最后一块

这个函数虽然原理比较简单,但是在 lodash 当中为了处理一些边界情况,还是很细致的对这个函数做了很多条件判断和处理,因此其健壮性很强,对应的可以对类似的类数组对象也做出处理

思路分析

1.png

源码分析

chunk

1. 传入参数:

  • array 传入一个数组
  • size 传入一个数字,决定新数组的元素个数,即将数组分成多少份
  • guard 传入迭代器对象,原意图是想配合迭代方法使用

2. 源码分析

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;
}

这里设置 guard 、或是没设置 size 情况下默认为 size 为 1;若是 size 传入其它则一律转成整数类型

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

判断 array 是否为空,然后判断传入的对象 length 属性是否存在,或是 size 是否为负数,如果没经过这里直接返回空数组

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

初始化,对分块数块等进行分配,通过 baseSlice 方法复制指定位置的元素到新数组,然后分配给 result。最终返回 result

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

baseSlice

1. 传入参数

  • array 传入一个数组
  • start 传入复制起点
  • end 传入复制终点

2. 源码分析

function baseSlice(array, start, end) {
  var index = -1,
      length = array.length;
  if (start < 0) {
    start = -start > length ? 0 : (length + start);
  }
  end = end > length ? length : end;
  if (end < 0) {
    end += length;
  }
  length = start > end ? 0 : ((end - start) >>> 0);
  start >>>= 0;
  var result = Array(length);
  while (++index < length) {
    result[index] = array[index + start];
  }
  return result;
}

初始化,将 startend 都转化为正数,注意如果 startend 原来是负数的话,会从数组末尾数 startend 数字

  var index = -1,
      length = array.length;
  if (start < 0) {
    start = -start > length ? 0 : (length + start);
  }
  end = end > length ? length : end;
  if (end < 0) {
    end += length;
  }

start 无符号右移, end - start 结果的符号位将成为正数,并取整。因为数字存储的是二进制补码,所以相当于对负数结果加上 2^32 ,保证其为非负数(有意义时取正,无意义时缺省 0 )。之后 index 从 0 开始,将对应 start 位置的 array 的元素,赋值给 index 位置的 result 的元素

 length = start > end ? 0 : ((end - start) >>> 0);
 start >>>= 0;
// 创建新数组
 var result = Array(length);
 while (++index < length) {
   result[index] = array[index + start];
 }
 return result;
}

类数组对象

因为 chunk 可以用于类数组对象的分组,因此这里也简单说下类数组对象

首先明确定义,有 length 属性,且其属性都是非负整数就可以称为类数组对象。

Chrome 浏览器中加上 splice 方法的对象展示就会从 {} 转化为 []

微信截图_20210312103946.png

lodash 中还有相应的判断方法 isArrayLike ,其中就是判断了 length 属性,以后有机会也会分析一下

微信截图_20210312103958.png

与数组不同,类数组对象的原型链,或者说其继承的祖先类型没有 Array,因此也不能使用 forEachmapreduce 等方法,也因此要对其迭代的话就需要用到 for 循环或是 for ……infor……of 因为需要对象本身需要可迭代,所以除了自定义了 Symbol.iterator 的情况外不可以使用

微信截图_20210312104017.png

可以后面附加一个自定义的 Symbol.iterator

微信截图_20210312104027.png

想要对类数组对象进行处理,除了上述的方法方法外,还可以将其直接转化为数组。Array.from 就是内置的处理这一情况的函数,传入参数之后会返回一个新的数组,数组元素都是按顺序排列的原对象属性

微信截图_20210312104043.png

类数组对象比较多见于 DOM 选择的结果,比如 document.querySelectorAll 会返回一个 NodeList 的类数组对象

微信截图_20210312104057.png

总结

chunk 方法作为一个数组方法,常用于对数组的切片分组,可以将数组进行升级维度,对数组进行分块

chunk 方法本身的健壮性也可以保证其可以用在类数组中

lodash 在底层实现时复用了 baseSlice 方法,因此 chunk 方法时间复杂度是 O(n^2)