# baseUniq
# Description
baseUniq 的作用是数组去重,是实现 uniq 、uniqBy 和 uniqWith 的内部方法,因此除了需要支持基本的去重操作外,还要支持 uniqBy 的 iteratee 参数和 uniqWith 的 comparator 参数。
# Params
(array, iteratee, comparator)
# Return
Array
# Depend
import SetCache from './SetCache.js'
import arrayIncludes from './arrayIncludes.js'
import arrayIncludesWith from './arrayIncludesWith.js'
import cacheHas from './cacheHas.js'
import createSet from './createSet.js'
import setToArray from './setToArray.js'
SetCache 源码分析
arrayIncludes 源码分析
arrayIncludesWith 源码分析
cacheHas 源码分析
createSet 源码分析
setToArray 源码分析
# Code
/** Used as the size to enable large array optimizations. */
const LARGE_ARRAY_SIZE = 200
function baseUniq(array, iteratee, comparator) {
let index = -1
let includes = arrayIncludes
let isCommon = true
const { length } = array
const result = []
let seen = result
if (comparator) {
isCommon = false
includes = arrayIncludesWith
}
else if (length >= LARGE_ARRAY_SIZE) {
const set = iteratee ? null : createSet(array)
if (set) {
return setToArray(set)
}
isCommon = false
includes = cacheHas
seen = new SetCache
}
else {
seen = iteratee ? [] : result
}
outer:
while (++index < length) {
let value = array[index]
const computed = iteratee ? iteratee(value) : value
value = (comparator || value !== 0) ? value : 0
if (isCommon && computed === computed) {
let seenIndex = seen.length
while (seenIndex--) {
if (seen[seenIndex] === computed) {
continue outer
}
}
if (iteratee) {
seen.push(computed)
}
result.push(value)
}
else if (!includes(seen, computed, comparator)) {
if (seen !== result) {
seen.push(computed)
}
result.push(value)
}
}
return result
}
# Analyze
首先定义了一系列的变量及对参数等处理
- 定义 includes
- 定义 isCommon
- 定义结果数组 result
- 拿到 array.length
判断了是否传入了
comparator
,如果传入了isCommon
为false
,includes
为arrayIncludesWith
如果没有传入
comparator
,那么会判断数组的长度是不是大于等于200if (comparator) { // ... } else if (length >= LARGE_ARRAY_SIZE) { const set = iteratee ? null : createSet(array) if (set) { return setToArray(set) } isCommon = false includes = cacheHas seen = new SetCache }
可以看到,如果没有传入 自定义处理函数
iteratee
时,会使用Set
来对数组进行去重,会判断如果set
为真值时,会调用setToArray
返回结果否则会将
isCommon
置为false
,并且将seen
设置为new SetCache
,对于大数组使用缓存处理,提升性能,这里同时也会将includes
改变如果既没有传入
comparator
,并且数组的长度也没有大于199,那么会判断是否传入了iteratee
if (comparator) { // ... } else if (length >= LARGE_ARRAY_SIZE) { // ... } else { seen = iteratee ? [] : result }
这里会根据是否传入了
iteratee
来决定seen
的值while 循环来进行元素比较,首先看元素值不是 NaN ,并且 isCommon 为 ture 的情况
outer: while (++index < length) { let value = array[index] const computed = iteratee ? iteratee(value) : value value = (comparator || value !== 0) ? value : 0 if (isCommon && computed === computed) { let seenIndex = seen.length while (seenIndex--) { if (seen[seenIndex] === computed) { continue outer } } if (iteratee) { seen.push(computed) } result.push(value) } }
可以看到首先拿到当前遍历的元素,然后会判断 是否传入了
iteratee
函数,也就是定义了computed
, 如果传入了iteratee
,则使用iteratee
处理过之后的值进行比较,否则还取value
对于
value
也会单独做一次处理,会将+0
和-0
转为 0, 但是如果传入了comparator
函数,这一点就会交给comparator
函数处理接着就是判断逻辑了,
while
循环,拿到seen
的每一项和computed
进行对比,如果找到相同的,则跳出外层循环,进行下一个值的迭代,因为当前值已经重复了如果 while 循环完成后,没有重复,则会判断 是否传入了 iteratee 函数。
可以结合之前的逻辑看到,如果没有传入
iteratee
函数,seen
和result
指向同一内存空间,所以,如果没有传入,则使用result
进行push
即可,如果传入了 则seen
和result
都要进行push
,毕竟判断的时候 使用的是seen
接着是处理
computed
为NaN
和isCommon
为false
的情况else if (!includes(seen, computed, comparator)) { if (seen !== result) { seen.push(computed) } result.push(value) }
这一块可以分这么几步来看
computed
为NaN
, 那到这里会使用arrayIncludes
或者arrayIncludesWith
来进行判断,如果不存在的话,则会判断seen
和result
是否指向了同一内存空间,如果指向同一内存空间,则只需要result
push
即可,否则seen
和result
都要push
- 传入了
comparator
函数,在传入了comparator
函数时,isCommon
也为false
,所以这个时候 这里的includes
函数其实就是arrayIncludesWith
- 没有传入
comparator
函数,但是数组的长度超过了199
, 并且 没有传入iteratee
函数时,这里会使用SetCache
来缓存数组,也就是includes
函数在这时,只需要seen
和computed
两个参数,并且 会将isCommon
置为false
,也就是 如果满足本条叙述,在迭代时也会一直走这个分支
在没有传入
iteratee
函数时,seen
和result
指向内存空间一致, 在while
循环中computed
和value
也是一样的,所以 根本不需要seen
去push
computed
,但是 如果传入了,那么比较时,使用的是处理之后的值,而此时seen
和result
也不指向同一内存空间,所以就需要seen
来push
computed
用作后续的比较
# Remark
Set MDN (opens new window) 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set 对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set 中的元素只会出现一次,即 Set 中的元素是唯一的。
# Example
const a = {a: 1}
console.log(baseUniq([a, a, 1, 1, 1, 1, 2, 3, 3, 4])) // [ { a: 1 }, 1, 2, 3, 4 ]