外部世界那些破舊與貧困的樣子,可以使我內心世界得到平衡。
——卡爾維諾《煙雲》
本文為讀 lodash 原始碼的第十七篇,後續文章會更新到這個倉庫中,歡迎 star:pocket-lodash
gitbook也會同步倉庫的更新,gitbook地址:pocket-lodash
作用與用法
baseDifference
可以用來獲取指定陣列與另一個陣列的差集。
這個函式是內部函式,是後面實現其它比較函式的核心函式。
baseDifference
的方法簽名如下:
baseDifference(array, values, iteratee, comparator)
複製程式碼
第一和第二個引數是需要比較的兩個陣列;iteratee
可以返回一值對映值,比較時,可以使用對映的值來進行比較; comparator
是自定義比較函式,如果有傳遞,則呼叫自定義的比較函式來進行交集的比較。
依賴
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
}
複製程式碼
iteratee的呼叫
if (iteratee) {
values = map(values, (value) => iteratee(value))
}
複製程式碼
如果有傳遞 iteratee
,則先呼叫 map
,使用 iteratee
生成要比較陣列的對映陣列 values
。
因為後面會有巢狀迴圈,避免重複呼叫 iteratee
,影響效能,所以一開始就需要生成 values
的對映陣列。
效能優化
這裡使用了 isCommon
來標誌是否使用普通方式來處理。
if (comparator) {
includes = arrayIncludesWith
isCommon = false
}
複製程式碼
如果有傳遞比較函式,則將 isCommon
標記為 false
,表示不用普通的方式來處理,後面可以看到,最後會使用 includes
方法來處理,也即 arrayIncludesWith
方法。
else if (values.length >= LARGE_ARRAY_SIZE) {
includes = cacheHas
isCommon = false
values = new SetCache(values)
}
複製程式碼
如果不需要使用自定義的比較方式,並且陣列較大時(這裡限定了200),則使用 SetCache
類來快取陣列。
SetChche
其實使用的是 Map/Set
或者物件的方式來儲存,避免大陣列巢狀迴圈時造成的效能損耗。
迴圈比較
接下來就遍歷第一個陣列 array
,將陣列中的每一項和第二個陣列的每一項比較。
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)
}
複製程式碼
可以看到,如果 isCommon
沒有標記為 false
, 或者需要比較的值 computed
不為 NaN
時,都採用巢狀迴圈的方式來比較。迴圈完畢,沒有在第二個陣列中發現相同的項時,將該項存入陣列 result
中。
如果 isCommon
為 false
或者需要比較的值為 NaN
時,則呼叫 includes
方法來比較。
由之前的分析得知:
- 如果指定
comparator
,則includes
為arrayIncludesWith
- 如果被比較的陣列
values
的長度超過200
,則includes
為cacheHas
- 否則,
includes
為arrayIncludes
+0與-0的處理
在看程式碼的時候,有一段十分奇怪:
value = (comparator || value !== 0) ? value : 0
複製程式碼
這段程式碼的意思是,在沒有提供 comparator
的情況下,如果 value === 0
,則將 value
賦值為 0
。
value === 0
時,可能為 +0
、-0
和 0
,lodash 為什麼要將它們都轉為 0
呢?
後來看到 lodash 作者在 issue 中說,因為比較會用到 Set
,而 Set
是不能區分 +0
和 -0
的。
參考
value = (comparator || value !== 0) ? value : 0; does it work?
License
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,所有文章都會同步傳送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面