用法
数组去重
uniq([2, 1, 2])
// => [2, 1]
解析
首先来看它的函数入口,没什么说的,就是判断数组是否有意义,有的话调用baseUniq方法
function uniq(array) {
return (array != null && array.length)
? baseUniq(array)
: []
}
接下来看看,baseUniq方法
首先会做一个类型的判断,判断是否使用常规的遍历,还是使用Set去重,或直接构造setCache。
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
//arrayIncludesWith方法通过传入的比较器来比较
includes = arrayIncludesWith
}
else if (length >= LARGE_ARRAY_SIZE) {
//如果传了遍历器,就构造成缓存对象
//如果没有传遍历器,就构造成set对象
//这里直接通过set对象的特性,完成数组去重
const set = iteratee ? null : createSet(array)
if (set) {
return setToArray(set)
}
isCommon = false
includes = cacheHas
seen = new SetCache
}
else {
//seen构造成新数组,或者指向result数组
seen = iteratee ? [] : result
}
然后我们来看看遍历部分的代码,大概的逻辑就是判断新数组中是否已经有了该元素,这里主要注意的就是NaN的判断
outer:
while (++index < length) {
let value = array[index]
const computed = iteratee ? iteratee(value) : value
value = (comparator || value !== 0) ? value : 0
//判断computed === computed 是排除NaN的情况
if (isCommon && computed === computed) {
let seenIndex = seen.length
while (seenIndex--) {
//遍历比较,如果有相同的则跳出
if (seen[seenIndex] === computed) {
continue outer
}
}
//iteratee存在就把计算属性放进去
if (iteratee) {
seen.push(computed)
}
result.push(value)
}
//如果用到了set或者setCache,就用includes方法判断
else if (!includes(seen, computed, comparator)) {
if (seen !== result) {
seen.push(computed)
}
result.push(value)
}
}
return result
}
然后来看看他的includes方法
//判断数组是否有意义
function arrayIncludes(array, value) {
const length = array == null ? 0 : array.length
return !!length && baseIndexOf(array, value, 0) > -1
}
//判断要查找的元素是否为NaN
function baseIndexOf(array, value, fromIndex) {
return value === value
? strictIndexOf(array, value, fromIndex)
: baseFindIndex(array, baseIsNaN, fromIndex)
}
//遍历比较数值
function strictIndexOf(array, value, fromIndex) {
let index = fromIndex - 1
const { length } = array
while (++index < length) {
if (array[index] === value) {
return index
}
}
return -1
}
//根据传入的遍历条件来判断,这里的是判断NaN
function baseFindIndex(array, predicate, fromIndex, fromRight) {
const { length } = array
let index = fromIndex + (fromRight ? 1 : -1)
while ((fromRight ? index-- : ++index < length)) {
if (predicate(array[index], index, array)) {
return index
}
}
return -1
}
最后来看看构造set对象的这块代码,setToArray很好理解,就是遍历生成数组,但在createSet函数中,做了一次判断,开始我也无法理解,后面在segmentfaultk看到了一个回答。
const set = iteratee ? null : createSet(array)
if (set) {
return setToArray(set)
}
const createSet = (Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY)
? (values) => new Set(values)
: () => {}
function setToArray(set) {
let index = -1
const result = new Array(set.size)
set.forEach((value) => {
result[++index] = value
})
return result
}
对于 Set 来说, -0 和 +0 是有区别的, 但在 es2015 后规范指定 -0 和 +0 相等. 存在浏览器兼容问题. 如果是遵循标准的,则 new Set([,-0]))[1] 返回 0, 否则会返回 -0, 1 / -0 的结果为 -Infinity 不等于 Infinity, 此时使用空对象来作为标准 Set 的替代实现