intersection()、intersectionBy()、intersectionWidth()
intersection()、intersectionBy()、intersectionWidth()方法用於對傳入的數組求交集,獲取存在於每個數組中的元素,生成新的數組返回,其內部都是基於baseIntersection方法;
剛開始看baseIntersection方法實現的時候可能會有點困難,它裏面引用了許多方法,讓理解起來有點困難,再看源碼之前,先思考一個問題,如果讓我們自己來實現一個方法,找出多個數組中都存在的元素並返回,我們該如何實現呢?
很容易能想到的就是:對於多個數組(array1、array2、array3 … ),由於我們最終想要的是存在於每個數組中的元素,所以我們可以對其中任意一個數組進行遍歷,讓其中的每個元素與剩餘數組進行對比,如果該元素在剩餘數組中都存在,那麼該元素就是我們最終想要的元素;
baseIntersection方法的實現跟上面的差不多,不同的地方是它不是任意選一個數組進行遍歷的,它總是取傳入數組參數的第一個數組進行遍歷,具體如下:
// baseIntersection.js
// baseIntersection方法用於求arrays中每個數組的交集元素,最終返回的交集元素值均來自arrays的第一個元素
function baseIntersection(arrays, iteratee, comparator) {
const includes = comparator ? arrayIncludesWith : arrayIncludes
// 獲取arrays數組第一個參數的長度
const length = arrays[0].length
// 獲取arrays數組的長度
const othLength = arrays.length
// 根據arrays數組的長度生成一個新數組caches
const caches = new Array(othLength)
const result = []
let array
let maxLength = Infinity
let othIndex = othLength
// 從後向前遍歷arrays數組
while (othIndex--) {
// 獲取當前索引在arrays數組中對應的數組對象
array = arrays[othIndex]
// 如果otherIndex索引大於0且iteratee函數存在,對當前數組對象調用map方法,也就是對當前數組對象中的每個元素調用iteratee方法,由其返回值組成一個新的數組返回
if (othIndex && iteratee) {
array = map(array, (value) => iteratee(value))
}
maxLength = Math.min(array.length, maxLength)
// 下面代碼的意思就是什麼時候使用緩存cache,有以下情況:
// 當comparator存在時不啓用;
// comparator不存在,iteratee存在時啓用
// comparator不存在,iteratee不存在,但是arrays數組第一個元素的長度大於等於120且遍歷的當前array數組的長度大於等於120時啓用
// 值得注意的是當othIndex爲0時,caches[0]是一個空的SetCache實例,因爲othIndex && array爲0
caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
? new SetCache(othIndex && array)
: undefined
}
// array取arrays數組中第一個元素
array = arrays[0]
// index爲-1,後面循環array數組時需要用到
let index = -1
// seen取caches數組中第一個元素
const seen = caches[0]
// 遍歷arrays中的第一個數組元素也就是array數組,將array數組的每個元素與arrays數組中剩餘的數組進行對比
outer:
while (++index < length && result.length < maxLength) {
// 獲取array數組中當前index對應的值
let value = array[index]
// 如果iteratee存在,則用iteratee處理value,後續操作將使用處理後的結果,如果iteratee不存在,則使用value進行後續操作
const computed = iteratee ? iteratee(value) : value
// 如果comparator存在或value不等於0時,value取自身,否則讓其爲0處理
value = (comparator || value !== 0) ? value : 0
// 判斷seen是否存在,存在的話,利用cacheHas方法判斷seen數組中是否包含computed元素
// 如果seen不存在,則調用includes方法,判斷返回數組result中是否包含computed
// cacheHas(seen, computed)判斷的目的是防止array中有相同元素且該元素存在於所有數組中時,再走一遍對比過程
// includes(result, computed, comparator)判斷的作用於seen中判斷是否有computed一致
/*
不過在看這兒代碼的時候有個疑問,那就是爲什麼seen中判斷的時候用的是computed而result中判斷的時候用value?
*/
if (!(seen
? cacheHas(seen, computed)
: includes(result, computed, comparator)
)) {
othIndex = othLength
// 從右向左循環遍歷剩餘arrays或caches數組,依次判斷arrays或caches數組中的每個數組中是否包含computed,如果遇到有數組不包含,則退出arrays數組的遍歷,繼續進行外層循環(也就是arrays第一個數組元素的遍歷)
// 遍歷時使用--otherIndex,這樣就不會遍歷到arrays或caches數組的第一個元素,也就是array或seen數組
while (--othIndex) {
const cache = caches[othIndex]
// 遍歷的時候先查看緩存是否存在,存在的話,使用caches[othIndex],不存在的話使用arrays[othIndex]
// 遍歷時,當前數組中不存在computed,則跳出當前的arrays或caches循環
if (!(cache
? cacheHas(cache, computed)
: includes(arrays[othIndex], computed, comparator))
) {
continue outer
}
}
// 遍歷完arrays或caches數組後,如果seen存在,則將computed添加到seen中
/*
爲什麼需要將computed添加到seen中呢?
這是因爲上面再循環arrays時,就是否啓用緩存有這麼一句:caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) ?
new SetCache(othIndex && array) :
undefined
其中對於arrays的第一個元素,也就是otherIndex爲0時,caches[0]是一個空的SetCache實例,其中沒有元素值,
當遍歷完arrays或caches數組沒有跳出循環時,說明computed是存在於所有數組中,
於是將其添加到seen中,防止array數組中相同元素在走一遍對比過程
*/
if (seen) {
seen.push(computed)
}
// 遍歷完arrays數組之後,如果沒有跳出循環,則表明computed在每個數組中都存在,於是將value添加到result中
result.push(value)
}
}
return result
}
看完了baseIntersection的實現方法,接下來就是分析intersection、intersectionBy、intersectionWidth方法的源碼,如下:
// intersection.js
function intersection(...arrays) {
// 對入參進行處理,遍歷參數數組arrays,並對其中每個元素調用castArrayLikeObject方法,使得arrays數組最終爲一個由類數組組成的數組
const mapped = map(arrays, castArrayLikeObject)
// 當mapped的長度大於0且mapped數組的第一個元素與arrays數組中的第一個元素相等時,調用baseIntersection方法,否則返回空數組
// 爲什麼要進行mapped[0] === arrays[0]的判斷?
// 因爲intersection方法實際上是對傳入的數組求交集元素,在得到交集元素後需要從第一個參數中這些交集元素返回,如果mapped[0]不等於arrays[0],說明arrays[0]並不是個類數組對象,無法將交集元素從其中返回
return (mapped.length && mapped[0] === arrays[0])
? baseIntersection(mapped)
: []
}
// intersectionBy.js
function intersectionBy(...arrays) {
// 取arrays最後一個元素作爲iteratee
let iteratee = last(arrays)
// 對入參進行處理,遍歷參數數組arrays,並對其中每個元素調用castArrayLikeObject方法,使得arrays數組最終爲一個由類數組組成的數組
const mapped = map(arrays, castArrayLikeObject)
// 判斷iteratee與mapped最後一個元素是否相等,相等說明iteratee是個類數組,而不是一個函數,則讓iteratee爲undefined,否則,說明iteratee是個函數,應該將mapped最後一個元素去除掉
if (iteratee === last(mapped)) {
iteratee = undefined
} else {
mapped.pop()
}
// 當mapped的長度大於0且mapped數組的第一個元素與arrays數組中的第一個元素相等時,調用baseIntersection方法,否則返回空數組
// 爲什麼要進行mapped[0] === arrays[0]的判斷?
// 因爲intersection方法實際上是對傳入的數組求交集元素,在得到交集元素後需要從第一個參數中這些交集元素返回,如果mapped[0]不等於arrays[0],說明arrays[0]並不是個類數組對象,無法將交集元素從其中返回
return (mapped.length && mapped[0] === arrays[0])
? baseIntersection(mapped, iteratee)
: []
}
// intersectionWidth.js
function intersectionWith(...arrays) {
// 取arrays最後一個元素作爲comparator
let comparator = last(arrays)
// 對入參進行處理,遍歷參數數組arrays,並對其中每個元素調用castArrayLikeObject方法,使得arrays數組最終爲一個由類數組組成的數組
const mapped = map(arrays, castArrayLikeObject)
// 判斷comparator是不是function,如果不是,comparator取undefined
comparator = typeof comparator == 'function' ? comparator : undefined
// 如果comparator存在,則將mapped最後一個元素去除掉
if (comparator) {
mapped.pop()
}
// 當mapped的長度大於0且mapped數組的第一個元素與arrays數組中的第一個元素相等時,調用baseIntersection方法,否則返回空數組
// 爲什麼要進行mapped[0] === arrays[0]的判斷?
// 因爲intersection方法實際上是對傳入的數組求交集元素,在得到交集元素後需要從第一個參數中這些交集元素返回,如果mapped[0]不等於arrays[0],說明arrays[0]並不是個類數組對象,無法將交集元素從其中返回
return (mapped.length && mapped[0] === arrays[0])
? baseIntersection(mapped, undefined, comparator)
: []
}
example:
_.intersection([1, 2, 2, 4], [2, 3, 2, 5])
// [2]
實現思路:
對於多個數組(array1、array2、array3 … ),由於我們最終想要的是存在於每個數組中的元素,所以我們選取array1數組進行遍歷,讓array1其中的每個元素與剩餘數組(array2、array3 …)進行對比,如果該元素在剩餘數組中都存在,那麼該元素就是我們最終想要的元素;
2019/6/22