lodash判断数组_lodash源码解析:chunk、slice、toInteger、toFinite、toNumber

本文深入解析lodash库中chunk、slice、toInteger、toFinite和toNumber等核心方法的源码,结合官方文档详细解读其功能和实现,并探讨纯JS实现的可能性。同时,提到了辅助方法isObject、isSymbol和getTag,以及参考的You-Dont-Need-Lodash-Underscore仓库。

31898087594a55daa93fcdaa6bdcd72b.png

上一阵子一直在忙着对前端界做出一点微小的贡献,再过两个多月就能揭晓。现在打算开一个天坑,把 lodash 的源码挨个解析一遍,学习下 npm 下载量最大、依赖最多的库的源码逻辑。

解析的代码为 2020 年 7 月 18 日的lodash 源码,版本是4.17.15,fork 到了自己的仓库中,顺序按照官网文档的顺序,在解析时会将该方法依赖的子方法也会全部分析下。有很多自己难理解或理解错的地方,抛砖引玉。

chunk

文档地址:https://lodash.com/docs/4.17.15#chunk

import slice from './slice.js';
import toInteger from './toInteger.js';

/**
 * 将数组(array)拆分成多个 size 长度的区块,并将这些区块组成一个新数组。
 * 如果array 无法被分割成全部等长的区块,那么最后剩余的元素将组成一个区块。
 *
 * @since 3.0.0
 * @category Array
 * @param {Array} array 需要处理的数组
 * @param {number} [size=1] 每个数组区块的长度
 * @returns {Array} 返回一个包含拆分区块的新数组(注:相当于一个二维数组)。
 * @example
 *
 * chunk(['a', 'b', 'c', 'd'], 2)
 * // => [['a', 'b'], ['c', 'd']]
 *
 * chunk(['a', 'b', 'c', 'd'], 3)
 * // => [['a', 'b', 'c'], ['d']]
 */
function chunk(array, size = 1) {
  // size必须大于等于0
  size = Math.max(toInteger(size), 0);
  // array为假时,length设为0;为真时设为数组长度
  const length = array == null ? 0 : array.length;
  // length为假或size为0时,返回空数组
  if (!length || size < 1) {
    return [];
  }
  // 初始化一个长度为(length / size并向上取整)的数组
  let index = 0;
  let resIndex = 0;
  const result = new Array(Math.ceil(length / size));

  // 循环取区块并赋值给result数组的对应位置
  while (index < length) {
    result[resIndex++] = slice(array, index, (index += size));
  }
  // 返回result
  return result;
}

export default chunk;

slice

文档地址:https://lodash.com/docs/4.17.15#slice

/**
 * 创建一个数组,来源是裁剪数组array,从 start 位置开始到 end 位置结束,但不包括 end 本身的位置。
 *
 * **注意:** 这个方法被用来代替
 * [`Array#slice`](https://mdn.io/Array/slice)确保返回的是个稠密数组。
 *
 * @since 3.0.0
 * @category Array
 * @param {Array} array 要裁剪的数组
 * @param {number} [start=0] 开始位置。负数索引将会被看作从数组结束位置的向前偏移。
 * @param {number} [end=array.length] 结束位置。负数索引将会被看作从数组结束位置的向前偏移。
 * @returns {Array} 返回剪切后的数组。
 * @example
 *
 * var array = [1, 2, 3, 4]
 *
 * _.slice(array, 2)
 * // => [3, 4]
 */
function slice(array, start, end) {
  // array是否为undefined或null,是的话则length为0
  let length = array == null ? 0 : array.length;
  //  length为假(undefined或0),则返回空数组
  if (!length) {
    return [];
  }
  // start是否为undefined或null,是的话则start赋值为0
  start = start == null ? 0 : start;
  // start是否为undefined,是的话则end赋值为length
  end = end === undefined ? length : end;
  // 如果start小于0
  if (start < 0) {
    // 防止真正的start变为负数
    start = -start > length ? 0 : length + start;
  }
  // 防止end比length还大
  end = end > length ? length : end;
  // 如果end小于0
  if (end < 0) {
    end += length;
  }
  // 如果start大于end时,length赋值0,否则就使用>>>移位0确保length是个正整数
  length = start > end ? 0 : (end - start) >>> 0;
  // 确保start是个正整数
  start >>>= 0;
  // 返回结果初始化
  let index = -1;
  const result = new Array(length);
  // 循环赋值
  while (++index < length) {
    result[index] = array[index + start];
  }
  // 返回
  return result;
}

export default slice;

toInteger

文档地址:https://lodash.com/docs/4.17.15#toInteger

import toFinite from './toFinite.js';

/**
 * 转换值为整数
 *
 * **注意:** 这个方法大致基于
 * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
 *
 * @since 4.0.0
 * @category Lang
 * @param {*} value 需要转换的值
 * @returns {number} 返回转换后的整数
 * @see isInteger, isNumber, toNumber
 * @example
 *
 * toInteger(3.2)
 * // => 3
 *
 * toInteger(Number.MIN_VALUE)
 * // => 0
 *
 * toInteger(Infinity)
 * // => 1.7976931348623157e+308
 *
 * toInteger('3.2')
 * // => 3
 */
function toInteger(value) {
  // 转换为有限数字
  const result = toFinite(value);
  // 对1取余
  const remainder = result % 1;
  // 能取到余数就减去余数,返回了整数值
  return remainder ? result - remainder : result;
}

export default toInteger;

toFinite

文档地址:https://lodash.com/docs/4.17.15#toFinite

import toNumber from './toNumber.js';

/** 用作各种“数字”常量的引用。 */
const INFINITY = 1 / 0;
const MAX_INTEGER = 1.7976931348623157e308;

/**
 * 将值转换为有限数字
 *
 * @since 4.12.0
 * @category Lang
 * @param {*} value 需要转换的值
 * @returns {number} 返回转换后的数字
 * @example
 *
 * toFinite(3.2)
 * // => 3.2
 *
 * toFinite(Number.MIN_VALUE)
 * // => 5e-324
 *
 * toFinite(Infinity)
 * // => 1.7976931348623157e+308
 *
 * toFinite('3.2')
 * // => 3.2
 */
function toFinite(value) {
  // 先判断value是否假
  if (!value) {
    // 假的话就判断是否为0,为0返回0,否则返回value
    return value === 0 ? value : 0;
  }
  // 把value转换为数字
  value = toNumber(value);
  // 判断是否正负无穷大
  if (value === INFINITY || value === -INFINITY) {
    // 是正负无穷大的话,则返回对应正负1.7976931348623157e+308
    const sign = value < 0 ? -1 : 1;
    return sign * MAX_INTEGER;
  }
  // value 如果!== value的情况,就是+0 === -0,此时返回0
  return value === value ? value : 0;
}

export default toFinite;

toNumber

文档地址:https://lodash.com/docs/4.17.15#toNumber

import isObject from './isObject.js';
import isSymbol from './isSymbol.js';

/** 用于各种Number类型的常量*/
const NAN = 0 / 0;

/** 用于匹配前面或者后面的空白 */
const reTrim = /^s+|s+$/g;

/** 用于检测错误的有符号十六进制字符串值 */
const reIsBadHex = /^[-+]0x[0-9a-f]+$/i;

/** 用于检测二进制字符串值 */
const reIsBinary = /^0b[01]+$/i;

/** 用于检测八进制字符串值 */
const reIsOctal = /^0o[0-7]+$/i;

/** 不依赖于root的内置方法引用 */
const freeParseInt = parseInt;

/**
 * 将目标值转换为数字
 *
 * @since 4.0.0
 * @category Lang
 * @param {*} value 目标值
 * @returns {number} 返回一个数字.
 * @see isInteger, toInteger, isNumber
 * @example
 *
 * toNumber(3.2)
 * // => 3.2
 *
 * toNumber(Number.MIN_VALUE)
 * // => 5e-324
 *
 * toNumber(Infinity)
 * // => Infinity
 *
 * toNumber('3.2')
 * // => 3.2
 */
function toNumber(value) {
  // 如果为number原始类型,则直接返回原值,否则继续
  if (typeof value === 'number') {
    return value;
  }
  // 如果为symbol类型则返回NaN,否则继续
  if (isSymbol(value)) {
    return NAN;
  }
  // 如果为对象类型,则先判断valueof属性是否typeof结果为function
  // 是的话就执行后赋值给other,否的话就valueof属性直接赋值给other
  //
  // 再判断other是否为对象,
  // 是对象则把toString结果赋值给value,否的话就other赋值给value。
  if (isObject(value)) {
    const other = typeof value.valueOf === 'function' ? value.valueOf() : value;
    value = isObject(other) ? `${other}` : other;
  }
  // 如果当前的value是否为string类型,
  // 不是string类型的话就判断是否为0,
  // 为0则直接返回,不为0就强制转换后返回
  if (typeof value !== 'string') {
    return value === 0 ? value : +value;
  }
  // 现在已经确定为string类型,掐头去尾
  value = value.replace(reTrim, '');
  // 判断是否为二进制
  const isBinary = reIsBinary.test(value);
  // 如果是二进制或八进制,就直接转换为数字返回
  return isBinary || reIsOctal.test(value)
    ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
    : // 不是的话就判断是否为有错的16进制,是的话返回Nan,否就转化为数字返回
    reIsBadHex.test(value)
    ? NAN
    : +value;
}

export default toNumber;

isObject

文档地址:https://lodash.com/docs/4.17.15#isObject

/**
 * 检查value是否为
 * [Object类型](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
 * 例如 arrays, functions, objects, regexes, `new Number(0)`, and `new String('')
 * @since 0.1.0
 * @category Lang
 * @param {*} value 需要检查的值
 * @returns {boolean} 如果对象则返回true,不是返回false
 * @example
 *
 * isObject({})
 * // => true
 *
 * isObject([1, 2, 3])
 * // => true
 *
 * isObject(Function)
 * // => true
 *
 * isObject(null)
 * // => false
 */
function isObject(value) {
  // 本质上使用typeof判断符来判断,这是以下类型的返回值,所以需要先排除null,并把function纳入进来
  // typeof null                'object'
  // typeof function() {}       'function'
  // typeof {}                  'object'
  // typeof []                  'object'
  const type = typeof value;
  return value != null && (type === 'object' || type === 'function');
}

export default isObject;

isSymbol

文档地址:https://lodash.com/docs/4.17.15#isSymbol

import getTag from './.internal/getTag.js';

/**
 * 检查目标值是否为Symbol原始类型或Symbol对象
 *
 * @since 4.0.0
 * @category Lang
 * @param {*} value 需要检查的值
 * @returns {boolean} 如果为symbol则返回true,否则返回false
 * @example
 *
 * isSymbol(Symbol.iterator)
 * // => true
 *
 * isSymbol('abc')
 * // => false
 */
function isSymbol(value) {
  // || 符号之后的判断是为了在ES2015之前的代码polyfill中检测symbol
  const type = typeof value;
  return (
    type == 'symbol' ||
    (type === 'object' && value != null && getTag(value) == '[object Symbol]')
  );
}

export default isSymbol;

getTag

const toString = Object.prototype.toString;

/**
 * 获取目标值的类型标签(Symbol.toStringTag)
 *
 * @private
 * @param {*} value 需要查询的值
 * @returns {string} 返回类型标签.
 */
function getTag(value) {
  // 如果==为假,则先检查是否为undefined,
  // 是的话返回[object Undefined],不是的话统统返回[object Null]
  if (value == null) {
    return value === undefined ? '[object Undefined]' : '[object Null]';
  }
  // 如==为真,直接借用Object原型链上的toString方法
  return toString.call(value);
}

export default getTag;

纯JS实现

在找lodash源码的时候发现了一个很有意思的仓库叫You-Dont-Need-Lodash-Underscore,使用纯JS实现了Lodash/Underscore的很多方法。在能明确自己的变量类型并且不想很重的引入lodash时可以自己写这些方法,同样来解析下它。

chunk

// Underscore/Lodash使用
_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]

_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]


// 原生实现

const chunk = (input, size) => {
  // arr是累计器
  // item是当前当前值
  // idx是当前索引
  // []是初始累计器
  // 整体的逻辑如下:
  // 当索引对size取余为0时,就在当前累计器的基础上加一个子数组[item]
  // 当索引对size取余不为0时,就在当前累计器基础上把最后一个子数组加上子元素item
  return input.reduce((arr, item, idx) => {
    return idx % size === 0
      ? [...arr, [item]]
      : [...arr.slice(0, -1), [...arr.slice(-1)[0], item]];// 这里活用了slice不改变原数组,返回新数组的规则
  }, []);
};

chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]

chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]

slice

slice就很尴尬了,Array.prototype上的原生方法。

// Lodash
var array = [1, 2, 3, 4]
console.log(_.slice(array, 1, 3))
// output: [2, 3]

// 原生
var array = [1, 2, 3, 4]
console.log(array.slice(1, 3));
// output: [2, 3]
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) File D:\信息计量学\python\.venv\Lib\site-packages\pandas\core\indexes\base.py:3812, in Index.get_loc(self, key) 3811 try: -> 3812 return self._engine.get_loc(casted_key) 3813 except KeyError as err: File pandas/_libs/index.pyx:167, in pandas._libs.index.IndexEngine.get_loc() File pandas/_libs/index.pyx:196, in pandas._libs.index.IndexEngine.get_loc() File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item() File pandas/_libs/hashtable_class_helper.pxi:7096, in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: '' The above exception was the direct cause of the following exception: KeyError Traceback (most recent call last) Cell In[12], line 2 1 # 数据清洗 ----> 2 data[clumn=="Organ_单位"] = data[""].str.strip().str.replace(" ", "").str.upper() File D:\信息计量学\python\.venv\Lib\site-packages\pandas\core\frame.py:4107, in DataFrame.__getitem__(self, key) 4105 if self.columns.nlevels > 1: 4106 return self._getitem_multilevel(key) -> 4107 indexer = self.columns.get_loc(key) 4108 if is_integer(indexer): 4109 indexer = [indexer] File D:\信息计量学\python\.venv\Lib\site-packages\pandas\core\indexes\base.py:3819, in Index.get_loc(self, key) 3814 if isinstance(casted_key, slice) or ( 3815 isinstance(casted_key, abc.Iterable) 3816 and any(isinstance(x, slice) for x in casted_key) 3817 ): 3818 raise InvalidIndexError(key) -> 3819 raise KeyError(key) from err 3820 except TypeError: 3821 # If we have a listlike key, _check_indexing_error will raise 3822 # InvalidIndexError. Otherwise we fall through and re-raise 3823 # the TypeError. 3824 self._check_indexing_error(key) KeyError: ''
06-12
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值