chunk函数,用于把一个数组按指定的size进行分组。对于不能平均分配的数组,该数组的最后一个分组会被剩余的元素填充,如[1, 2, 3],size 为2,最终返回[[1, 2], [3]]。
本文包含类型校验isObejct,isSymbol,类型转换toInteger,toNumber,toFinite以及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
通过typeof和Object.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_VALUE和Number.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的干预,而不传precision的ceil函数等同于直接调用Math.ceil。更多详情参考lodash 取整函数 ceil round floor。
如有问题,可以在评论区提出,我会积极改进。本篇文章如果对你有帮助,别忘了点赞哦~~