A modern JavaScript utility library delivering modularity, performance & extras.
lodash
是一个一致性、模块化、高性能的 JavaScript
实用工具库
一、环境准备
git clone https://github.com/lodash/lodash.git
cd axios
npm install
npm run test
二、结构分析
这是一张 differenceWith
依赖引用路径图,还记得我们之前分析的 difference
,differenceBy
相比 difference
只多了一个 last
依赖,下面将会全篇分析一下设计思路,详情部分可以关注前面的 Difference、Difference(Cache)、Difference(Flatten)、Difference(Index)。
三、模块分析
differenceWith
这个方法类似_.difference ,除了它接受一个 comparator (比较器),调用它来比较’array’和’values’的元素。命令和结果值的引用由第一个数组确定。比较器通过两个参数调用:(arrVal,othVal)。
import baseDifference from './.internal/baseDifference.js'
import baseFlatten from './.internal/baseFlatten.js'
import isArrayLikeObject from './isArrayLikeObject.js'
import last from './last.js'
function differenceWith(array, ...values) {
let comparator = last(values)
if (isArrayLikeObject(comparator)) {
comparator = undefined
}
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
: []
}
export default differenceWith
- 使用
last
获取 ...values
解构的最后一个参数即为 comparator
- 会返回一个过滤后的数组,下面是一个官方示例,其中
_.isEqual(value, other)
执行深比较来确定两者的值是否相等。
var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
_.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
1. internal/baseDifference
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'
const LARGE_ARRAY_SIZE = 200
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
- 若入参包含了自定义比较器
comparator
则设定非通用排除规则 includes
(isCommon = false),需要注意的是 values.length >= LARGE_ARRAY_SIZE = 200
也要设定非通用排除规则 includes
,此时需要对数据做 map
存储,牺牲更多空间换取更少的查询时间。
1.1 SetCache
import MapCache from './MapCache.js'
const HASH_UNDEFINED = '__lodash_hash_undefined__'
class SetCache {
constructor(values) {
let index = -1
const length = values == null ? 0 : values.length
this.__data__ = new MapCache
while (++index < length) {
this.add(values[index])
}
}
add(value) {
this.__data__.set(value, HASH_UNDEFINED)
return this
}
has(value) {
return this.__data__.has(value)
}
}
SetCache.prototype.push = SetCache.prototype.add
export default SetCache
1.2 arrayIncludes
不支持从数组指定位置搜索的includes
import baseIndexOf from './baseIndexOf.js'
function arrayIncludes(array, value) {
const length = array == null ? 0 : array.length
return !!length && baseIndexOf(array, value, 0) > -1
}
export default arrayIncludes
-
如果 length
不存在(包含null、0、undefined)或者没有找到 index
都会返回 false
-
baseIndexOf: 根据待查找值是否为 NaN
分别进入严格查找逻辑 strictIndexOf
或基础查找逻辑 baseFindIndex
-
baseFindIndex: findIndex
和 findLastIndex
的基本实现,支持正向/反向查找 index
-
baseIsNaN: 会过滤掉 NaN
,因为在执行自比较之中 NaN
,也只有 NaN
,比较之中不等于它自己,即 NaN === NaN; // false
-
strictIndexOf: indexOf
的一个特殊版本,它执行严格的相等 ===
用于比较 value
1.3 arrayIncludesWith
function arrayIncludesWith(array, target, comparator) {
if (array == null) {
return false
}
for (const value of array) {
if (comparator(target, value)) {
return true
}
}
return false
}
export default arrayIncludesWith
- 类似于
arrayIncludes
,只是它接受一个比较器 comparator
。使用 for...of
迭代待搜索数组 array
中的每一项,使用 if
判断比较器 comparator(target, value)
的返回值并给出对应返回结果,目前用不到,将会在下面的 differenceWith
当中出场 🐶
1.4 map
function map(array, iteratee) {
let index = -1
const length = array == null ? 0 : array.length
const result = new Array(length)
while (++index < length) {
result[index] = iteratee(array[index], index, array)
}
return result
}
export default map
- 通过
iteratee
运行 array
的每个元素来创建一个数组 result
。使用 new Array
创建一个对应其长度的数组 result
,其中 array
为 null
时,长度为 0
,将会创建一个空数组,会按照 array
长度循环调用 iteratee
,每次循环 index + 1
。
1.5 cacheHas
function cacheHas(cache, key) {
return cache.has(key)
}
export default cacheHas
- 检查
key
的 cache
值是否存在,cache
和 key
均为入参
2. internal/baseFlatten
import isFlattenable from './isFlattenable.js'
function baseFlatten(array, depth, predicate, isStrict, result) {
predicate || (predicate = isFlattenable)
result || (result = [])
if (array == null) {
return result
}
for (const value of array) {
if (depth > 0 && predicate(value)) {
if (depth > 1) {
baseFlatten(value, depth - 1, predicate, isStrict, result)
} else {
result.push(...value)
}
} else if (!isStrict) {
result[result.length] = value
}
}
return result
}
export default baseFlatten
- 扁平化的基本实现,使用
for...of
迭代待展平 array
中的每一项,如果最大递归深度 depth
仍然未减至 1
则递归调用 baseFlatten
,每次depth - 1
,直至 depth = 1
将返回值放入 result
。 depth = 1
时由于所有项都已展平 predicate(value)
返回 false
,进入 else if (!isStrict)
语句块,目的是限制“谓词”展平到result
,其中谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。通俗的说就是一个函数,会返回一个符合该条件(“truthy值”)的数组。
2.1 isFlattenable
import isArguments from '../isArguments.js'
const spreadableSymbol = Symbol.isConcatSpreadable
function isFlattenable(value) {
return Array.isArray(value) || isArguments(value) ||
!!(value && value[spreadableSymbol])
}
export default isFlattenable
-
重点关注 value[spreadableSymbol]
,在这之前我们需要知道 Array
的 concat
运算,正常情况下 ['a', 'b', 'c'].concat([1, 2, 3]) = ["a", "b", "c", 1, 2, 3]
,但可通过设定被连接 array
,array[Symbol.isConcatSpreadable] = false;
,使得 array
不被展开到发起连接的 array
而是作为一个元素连接到其中,如 ['a', 'b', 'c'].concat([1, 2, 3]) = ["a", "b", "c", [ 1, 2, 3] ]
isArguments: `arguments` 对象是所有(非箭头)函数中都可用的局部变量,可以按照操作数组的方式获取参数,封装了 `getTag` 和 `isObjectLike`。
getTag: 封装了 `Object` 原型链函数 `toString()`,借助 `toString()` 判断属性类型的性质判断 `value` 是否为 `Undefined` 或者 `Null`
isObjectLike: 借助 `typeof` 可以获取 `未经计算的操作数` 的类型的性质判断 `value` 是否为非 `Null` 的 `object` 类型(js设计缺陷使得 `typeof null = Object`)
3. isArrayLikeObject
import isArrayLike from './isArrayLike.js'
import isObjectLike from './isObjectLike.js'
function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value)
}
export default isArrayLikeObject
-
封装了 isObjectLike
与 isArrayLike
,当 value
同时符合两者所检测的目标类型时返回 true
,否则返回 false
isObjectLike: 借助 `typeof` 可以获取 `未经计算的操作数` 的类型的性质判断 `value` 是否为非 `Null` 的 `object` 类型(js设计缺陷使得 `typeof null = Object`)
isArrayLike: 会判断一个值非 `null`,非 `function` 类型,有 `.length` 属性且 `length` 为大小不超过 `MAX_SAFE_INTEGER = 9007199254740991` 的自然数
3.1 isArrayLike
import isLength from './isLength.js'
function isArrayLike(value) {
return value != null && typeof value !== 'function' && isLength(value.length)
}
export default isArrayLike
- 检查
value
是否与数组类似。其被视为数组,不是函数但是有 .length
值,是一个大于等于0
且小于 MAX_SAFE_INTEGER
的 Number
- 重点关注
isLength
,判断规则是 typeof value === 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER
,其中 value % 1 == 0
确保 value
是整数,MAX_SAFE_INTEGER = 9007199254740991
3.2 isObjectLike
function isObjectLike(value) {
return typeof value === 'object' && value !== null
}
export default isObjectLike
- 可以通过
typeof
来获取 未经计算的操作数
的类型,这里检查 value
是否与对象类似,如果不为空则是一个对象,并且会有一个typeof
运算结果为 object
返回值
4. last
function last(array) {
const length = array == null ? 0 : array.length
return length ? array[length - 1] : undefined
}
export default last
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)