鲁迅说过:只有阅读过优秀库源码的人,才能配的上是真正的勇士。
difference
创建一个具有唯一array值的数组,每个值不包含在其他给定的数组中。(注:即创建一个新数组,这个数组中的值,为第一个数字(array 参数)排除了给定数组中的值。)该方法使用SameValueZero做相等比较。结果值的顺序是由第一个数组中的顺序确定。
换句话来说这个函数能够对比两个数组,并且以第一个参数数组为目标数组,第二个参数数组为过滤数组,最后返回过滤后的数据。
function difference(array, ...values) {
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
: []
}
difference所依赖的工具函数如下:
- isArrayLikeObject
- baseDifference
- isArrayLikeObject
- baseFlatten
1.isArrayLikeObject
该工具函数主要用来判断数组是否是对象。
isArrayLikeObject主要依赖以下函数:
function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value)
}
1.1. isObjectLike
1.2. isArrayLike
1.1 isObjectLike
该工具函数只要用来判断是否是对象和不是null的元素
function isObjectLike(value){
return typeof ==='object' && value!==null
}
知识点:
typeof的用法:
操作符返回一个字符串,表示未经计算的操作数的类型。
typeof {} // 'object'
typeof [] // 'object'
typeof Function // 'function'
typeof null // 'object'
typeof new String(1) // 'object'
typeof String(1) // 'string'
typeof /\d+/ // 'object'
typeof undefined // 'undefined'
1.2 isArrayLike
检查value是否是数组或者是集合。一个值如果被认定为是数组或者是集合,那么它必须不是一个Funtion和有length属性,并且length属性可以等于0并且是小于Number.MAX_SAFE_INTEGER
(9007199254740991)
function isArrayLike(value) {
return value != null && typeof value !== 'function' && isLength(value.length)
}
function isLength(value) {
return typeof value === 'number' &&
value > -1 && value % 1 == 0 && value <= Number.MAX_SAFE_INTEGER
}
可以看出isLength这个函数很有意思,特别是value % 1 == 0,基本上是杜绝的value是小数点的情况,而且从中也可以得知原来Function是有length属性,也算是补全了一个知识盲点。
2.baseDifference
这个函数所实现的功能给定一个待检查的数组 array,然后第二个参数则为需要排除的数组
源码如下:
import SetCache from './SetCache.js'
import arrayIncludes from './arrayIncludes.js'
import arrayIncludesWith from './arrayIncludesWith.js'
import map from '../map.js'
import cacheHas from './cacheHas.js'
/** Used as the size to enable large array optimizations. */
const LARGE_ARRAY_SIZE = 200
/**
* The base implementation of methods like `difference` without support
* for excluding multiple arrays.
*
* @private
* @param {Array} array The array to inspect. // 要检查的数组
* @param {Array} values The values to exclude. // 需要排除的数组
* @param {Function} [iteratee] The iteratee invoked per element. // 每个元素调用的迭代函数
* @param {Function} [comparator] The comparator invoked per element. // 每个元素的比较器函数
* @returns {Array} Returns the new array of filtered values. // 返回过滤后的数组
*/
function baseDifference(array, values, iteratee, comparator) {
let includes = arrayIncludes
let isCommon = true
const result = []
const valuesLength = values.length
if (!array.length) {
return result
}
if (iteratee) {
values = map(values, (value) => iteratee(value))
}
if (comparator) {
includes = arrayIncludesWith
isCommon = false
}
else if (values.length >= LARGE_ARRAY_SIZE) {
includes = cacheHas
isCommon = false
values = new SetCache(values)
}
outer:
for (let value of array) {
const computed = iteratee == null ? value : iteratee(value)
value = (comparator || value !== 0) ? value : 0
if (isCommon && computed === computed) {
let valuesIndex = valuesLength
while (valuesIndex--) {
if (values[valuesIndex] === computed) {
continue outer
}
}
result.push(value)
}
else if (!includes(values, computed, comparator)) {
result.push(value)
}
}
return result
}
export default baseDifference
baseDifference所涉及到的函数比较多,所以只能一个一个进行解析,涉及到的函数如下:
- arrayIncludes
其实这个函数类似与es6中,Array.prototype.includes()方法,也就是说给定一个数组和一个value值判断该value是否存在与array中。
import baseIndexOf from './baseIndexOf.js';
/**
* 数组`includes`方法的的特殊版本,不支持指定开始搜索位置的索引。
*
* @private
* @param {Array} [array] 要检查的数组
* @param {*} target 要搜索的值
* @returns {boolean} 如果搜索到了目标则返回true,否则返回false。
*/
function arrayIncludes(array, value) {
// 初始化length
const length = array == null ? 0 : array.length;
// 当length不为0且能搜到value时,返回true,否则返回false。
return !!length && baseIndexOf(array, value, 0) > -1;
}
export default arrayIncludes;
从上源码中也可以感觉的出来baseIndexOf使用获取value在array中的下标。
知识点:
array == null 这样的判断方式也可以判断array为undefine的情况。
- baseIndexOf
给定一个array和一个value,返回该value在array中的下标
import baseFindIndex from './baseFindIndex.js';
import baseIsNaN from './baseIsNaN.js';
import strictIndexOf from './strictIndexOf.js';
/**
* indexOf的基础实现,不使用fromIndex边界检查
*
* @private
* @param {Array} array 要检查的数组
* @param {*} value 要搜索的值
* @param {number} fromIndex 搜索开始位置的索引
* @returns {number} 返回匹配值的索引,否则返回-1
*/
function baseIndexOf(array, value, fromIndex) {
// value为NaN的时候,调用baseFindIndex;value不为NaN的时候,调用strictIndexOf
return value === value
? strictIndexOf(array, value, fromIndex)
: baseFindIndex(array, baseIsNaN, fromIndex);
}
export default baseIndexOf;
知识点:
value === value,一般的数据类型value,都会返回true,那么这样的比较意义何在呢?后面通过翻阅mdn可以知道 NaN === NaN返回的结果是false。那么也就是说该函数考虑到value为NaN的情况。
如果value不是NaN的情况,则调用strictIndexOf()函数。
- strictIndexOf
该函数会返回基本类型值的下标。
/**
* indexOf的一个特定版本,对value执行严格相等比较,即 ===。
*
* @private
* @param {Array} array 要检查的数组
* @param {*} value 要搜索的值
* @param {number} fromIndex 搜索开始位置处的索引
* @returns {number} 返回匹配值的索引,否则返回-1
*/
function strictIndexOf(array, value, fromIndex) {
// 把fromIndex位置处的值包含进来
let index = fromIndex - 1;
// 取到array.length
const { length } = array;
// 迭代
while (++index < length) {
// 比较时使用===
if (array[index] === value) {
// 返回搜索到的索引
return index;
}
}
// 返回-1
return -1;
}
export default strictIndexOf;
我对于fromIndex这个参数是实属有点难理解,为什么需要添加这个参数呢?难道有场景不是从0开始需要元素的吗?
如果value是NaN则会调用baseFindIndex()函数
- baseFindIndex
baseIsNaN
/**
* isNaN的实现基础,不支持对象形式的数字。
*
* @private
* @param {*} value 要检查的值
* @returns {boolean} 如果value是NaN则返回true,否则返回false。
*/
function baseIsNaN(value) {
// 只有NaN !== NaN
return value !== value;
}
export default baseIsNaN;
/**
* `findIndex` 和 `findLastIndex`的实现基础
*
* @private
* @param {Array} array 打算检索的数组
* @param {Function} predicate 每次迭代时调用的函数
* @param {number} fromIndex 起始处的索引位置
* @param {boolean} [fromRight] 指定是否从右向左迭代
* @returns {number} 返回找到元素的 索引值(index),否则返回 -1。
*/
function baseFindIndex(array, predicate, fromIndex, fromRight) {
// 取到array.length
const { length } = array;
// 如果从左向右,起始索引位置就是fromIndex - 1;如果从右向左,起始索引位置是 fromIndex + 1。
// 目的是迭代时能把fromIndex包进去。
let index = fromIndex + (fromRight ? 1 : -1);
// 迭代时,从左向右则index递增,从右向左则index递减
while (fromRight ? index-- : ++index < length) {
// 在这里定义了predicate函数的参数(item, index, array),找到了想要的元素则返回true,predicate函数指的是baseIsNaN(),虽然传递了三个参数,但是baseIsNaN只接受第一个参数。
if (predicate(array[index], index, array)) {
// 返回想寻找元素的索引
return index;
}
}
// 所有的元素都不匹配,就返回 -1。
return -1;
}
export default baseFindIndex;
知识点:
我发现lodash很喜欢通过函数传递函数这样的方式来进行操作,或许这样更利于业务函数的扩展吧。