lodash 数组分组函数 chunk

1,486 阅读5分钟

chunk函数,用于把一个数组按指定的size进行分组。对于不能平均分配的数组,该数组的最后一个分组会被剩余的元素填充,如[1, 2, 3]size 为2,最终返回[[1, 2], [3]]

本文包含类型校验isObejctisSymbol,类型转换toIntegertoNumbertoFinite以及chunk函数本身。

类型校验

getTag

调用Object.prototype.toString方法

const toString = Object.prototype.toString
function getTag(value) {
  if (value == null) {
    // 兼容javascript低版本,特殊处理null和undefined
    return value === undefined ? '[object Undefined]' : '[object Null]'
  }
  return toString.call(value)
}

isSymbol

通过typeofObject.prototype.toString对传入的value进行校验。typeof value === 'symbol' 或者 Object.prototype.toString.call(value) === '[object Symbol]'

依赖于getTag函数

function isSymbol(value) {
  const type = typeof value
  return type == 'symbol' || (type === 'object' && value != null && getTag(value) == '[object Symbol]')
}

isObject

判断传入的value是否时一个对象。ecma262关于Object的定义

在JavaScript中,对象可以是一个普通对象,也可以是函数,正则表达式对象以及通过new调用的基础数据类型的构造函数所生成的实例等

function isObject(value) {
  const type = typeof value
  // 例如:普通的对象,函数,数组,正则表达式对象,String构造函数生成的对象,Number构造函数生成的对象等等
  // 关于Object类型的定义 https://262.ecma-international.org/7.0/#sec-object-type
  return value != null && (type === 'object' || type === 'function')
}

类型转换

toNumber

该函数是ToNumber的实现,但不如标准的toNumber那么严格。兼容了Symbol的情况

通过该函数的能学习到什么?

  • 在JavaScript中,获取对象的原始数据值,默认会调用对象上的valueOf方法,许多内置的对象都实现了自身的valueOf方法。比如new Date() > 1000会默认调用Date.prototype.valueOf方法进行隐式类型转换后进行比较。。关于Object.prototype.valueOf方法的说明
  • 我们常用+运算符将一个非数字类型转换为数字。+的计算规则遵循ToNumber。在JavaScript中,我们可以通过这样的方式无缝的转换十六进制
  • 十六进制中的正负数并不像十进制那样通过+-号来区分。
  • 我们可以通过parseInt(string, radix)方法转换二进制和八进制数

源码如下:

/** Used as references for various `Number` constants. */
// 生成not a number 的常量
const NAN = 0 / 0

/** Used to match leading and trailing whitespace. */
// 匹配开头或结尾的空格
const reTrim = /^\s+|\s+$/g

/** Used to detect bad signed hexadecimal string values. */
// 匹配以 - 或者 + 开头的错误的十六进制数字
const reIsBadHex = /^[-+]0x[0-9a-f]+$/i

/** Used to detect binary string values. */
// 匹配二进制数字
const reIsBinary = /^0b[01]+$/i

/** Used to detect octal string values. */
// 匹配八进制数字
const reIsOctal = /^0o[0-7]+$/i

/** Built-in method references without a dependency on `root`. */
// parseInt内部方法的引入
// parseInt(string, radix)
const freeParseInt = parseInt

function toNumber(value) {
  // 十进制数字类型直接返回
  if (typeof value === 'number') {
    return value
  }
  // symbol类型返回NaN
  if (isSymbol(value)) {
    return NAN
  }
  // 对象
  if (isObject(value)) {
    // 优先调用valueOf方法
    const other = typeof value.valueOf === 'function' ? value.valueOf() : value
    // 其次调用toString方法
    value = isObject(other) ? `${other}` : other
  }
  // value不是string类型
  if (typeof value !== 'string') {
    // +0 -0 0 直接返回 否则转换成数字
    return value === 0 ? value : +value
  }
  // 去除前后空格
  value = value.replace(reTrim, '')
  // 是否是二进制数
  const isBinary = reIsBinary.test(value)
  // 二进制或八进制 截取0b 0o 调用parseInt转换
  return (isBinary || reIsOctal.test(value))
    // 调用parseInt方法
    ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
    // 错误的十六进制返回NaN,正确的十六进制直接转换
    : (reIsBadHex.test(value) ? NAN : +value)
}

toFinite

该函数用于把大于Number.MAX_VALUE的数字转换为Number.MAX_VALUE,小于Number.MIN_VALUE的数字同理。该函数依赖于上面的toNumber函数。

Number.MAX_VALUENumber.MIN_VALUE是JavaScript中能表示的最大数字和最小数字。大于这个范围(Number.MAX_VALUE - Number.POSITIVE_INFINITY)的需要用BigInt类型来表示。

通过该函数我们能学习到什么?

  • NaN === NaN 返回false,但Object.is(NaN, NaN)返回true

源码:

const INFINITY = 1 / 0

// 约等于console.log(Number.MAX_VALUE),这里约等于的意思是JavaScript无法正常表示大于Math.pow(2, 53) - 1 的数
const MAX_INTEGER = 1.7976931348623157e+308

function toFinite(value) {
  // value falsy
  if (!value) {
    return value === 0 ? value : 0
  }
  // 转换为Number类型,不支持十六进制
  value = toNumber(value)
  // value值为INFINITY 或 -INFINITY 返回约等于  Number.MAX_VALUE 和 Number.MIN_VALUE的值
  if (value === INFINITY || value === -INFINITY) {
    const sign = (value < 0 ? -1 : 1)
    return sign * MAX_INTEGER
  }

  // value不为NaN的情况下,返回value,,否则返回0
  // NaN === NaN false
  return value === value ? value : 0
}

toInteger

整数转换函数,依赖于toFinite函数。

通过该函数我们能学习到什么?

  • 通过取余运算%我们能获取到数字的小数部分,并且该小数部分包含了浮点数运算误差。比如1.1%1 = 0.10000000000000009

源码:

function toInteger(value) {
  // 转换为Number.MAX_VALUE Number.MIN_VALUE范围内的值
  const result = toFinite(value)
  // 浮点数截取小数部分
  const remainder = result % 1
  // 取整返回
  return remainder ? result - remainder : result
}

slice

该函数与Array.prototype.slice的功能一致,唯一不同点在于该函数会返回一个密集型数组。即对于数组中的empty item,该函数会用undefined进行填充。

通过该函数我们能学习到什么?

  • 可以通过位运算无符号右移对数字进行取整3.333>>>0 = 3

源码如下:

function slice(array, start, end) {
  let length = array == null ? 0 : array.length
  // length为0返回空数组
  if (!length) {
    return []
  }
  // start为null 或 undefined 默认为0
  start = start == null ? 0 : start
  // end 为 undefined 默认数组长度
  end = end === undefined ? length : end

  // start 为负数 则从后向前定位起始位置
  if (start < 0) {
    start = -start > length ? 0 : (length + start)
  }
  // end 最大不能超过数组长度
  end = end > length ? length : end
  // end为负数,从后向前确定截止位置
  if (end < 0) {
    end += length
  }
  // 开始位置大于截止位置返回0 否则
  // 无符号右移 取整
  length = start > end ? 0 : ((end - start) >>> 0)
  // 无符号右移 取整
  start >>>= 0

  // 返回对应的数组
  let index = -1
  const result = new Array(length)
  while (++index < length) {
    result[index] = array[index + start]
  }
  return result
}

chunk

函数依赖于slice函数和toInteger函数。

源码如下:

function chunk(array, size = 1) {
  // size 支持十进制 二进制 和 八进制
  size = Math.max(toInteger(size), 0)
  // length 默认为0 或 数组长度
  const length = array == null ? 0 : array.length
  // 数组长度为0 或 非数组 或 传入的size小于默认值1 默认返回空数组
  if (!length || size < 1) {
    return []
  }
  // 待处理数组的index
  let index = 0
  // 结果数组的 index
  let resIndex = 0
  // 根据size和length生成一个新的数组,想上取整,如 9/2 = 4.5 Math.ceil(4.5) = 5 生成一个长度为5的数组,其中前4项的length为2,最后一项为1
  const result = new Array(Math.ceil(length / size))

  // 填充并返回新数组
  while (index < length) {
    result[resIndex++] = slice(array, index, (index += size))
  }
  return result
}

此处的ceil函数并没有依赖于lodash自身实现的ceil函数。原因在于不需要第二个参数precision的干预,而不传precisionceil函数等同于直接调用Math.ceil。更多详情参考lodash 取整函数 ceil round floor

如有问题,可以在评论区提出,我会积极改进。本篇文章如果对你有帮助,别忘了点赞哦~~