lodash.js包是node開發中經常使用的js工具包,裏面有許多實用的方法,今天分析經常使用的一個去重方法---uniq
_.uniq([2, 1, 2]) // => [2, 1]
// uniq.js import baseUniq from './.internal/baseUniq.js' function uniq(array) { return (array != null && array.length) ? baseUniq(array) : [] } export default uniq
能夠看到,uniq函數這邊只作了一個針對baseUniq的封裝,因此繼續看baseUniq源碼😂javascript
// baseUniq.js 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' const LARGE_ARRAY_SIZE = 200 // 做爲數組處理的最大數組長度 function baseUniq(array, iteratee, comparator) { let index = -1 let includes = arrayIncludes // 向下兼容,內部使用使用while作循環 let isCommon = true const { length } = array const result = [] let seen = result if (comparator) { // 若是有comparator,標註爲非普通函數處理 isCommon = false includes = arrayIncludesWith // includes 判重方法更換爲 arrayIncludesWith } else if (length >= LARGE_ARRAY_SIZE) { // 長度超過200後啓用,大數組優化策略 // 判斷是否有迭代器,沒有則設爲Set類型(支持Set類型的環境直接調用生成Set實例去重) const set = iteratee ? null : createSet(array) if (set) { return setToArray(set) //Set類型轉數組(Set類型中不存在重複元素,至關於去重了)直接返回 } isCommon = false // 非普通模式 includes = cacheHas // includes 判重方法更換爲hash判斷 seen = new SetCache // 實例化hash緩存容器 } 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 // 直接跳出當前循環直接進入下一輪outer: } } if (iteratee) { // 有迭代器的狀況下 seen.push(computed) // 結果推入緩存容器 } result.push(value) // 追加入結果數組 } // 非正常數組處理模式下,調用includes方法,判斷緩存容器中是否存在重複的值 else if (!includes(seen, computed, comparator)) { if (seen !== result) { // 非普通模式下,result和seen內存空間地址不同 seen.push(computed) } result.push(value) // 追加入結果數組 } } return result // 循環完成,返回去重後的數組 } export default baseUniq
1.注意下面的代碼:java
else if (length >= LARGE_ARRAY_SIZE) { // 長度超過200後啓用,大數組優化策略 // 判斷是否有迭代器,沒有則設爲Set類型(支持Set類型的環境直接調用生成Set實例去重) const set = iteratee ? null : createSet(array) if (set) { return setToArray(set) //Set類型轉數組(Set類型中不存在重複元素,至關於去重了)直接返回 } }
lodash 會去判斷當前數組的長度,若是數組過大會調用ES6的新的Set數據類型,Set類型中不會存在重複的元素。也就是說作到了數組的去重,最後調用setToArray方法返回數組,Set類型是可迭代的類型,可使用 ...
擴展運算符。
在性能方面,由於js是單線程執行,大數組的循環會長時間佔用CPU時間,致使線程被阻塞。而使用Set類型後將去重的工做交個底層去處理,提升了性能。因此平時在有去重需求時能夠考慮Set類型的去重,而不是在js執行層去作循環,也是一種性能優化。node
2.接着看不採用Set去重的代碼處理策略:數組
outer: while (++index < length) { // 循環遍歷每個元素 let value = array[index] // 取出當前遍歷值 value = (comparator || value !== 0) ? value : 0 if (isCommon && computed === computed) { // 普通模式執行下面代碼 let seenIndex = seen.length // 取當前容器的長度爲下一個元素的角標 while (seenIndex--) { // 循環遍歷每個容器中每個元素 if (seen[seenIndex] === computed) { // 匹配到重複的元素 continue outer // 直接跳出當前循環直接進入下一輪outer: } } } }
能夠看到這裏使用兩個嵌套while去遍歷數組,並判斷是否存在重複元素。這樣的邏輯並無問題,也是平時工做中最多見的去重代碼邏輯,代碼的執行時間複雜度爲 O(n^2),執行時間會隨着數組的增大指數級增長,因此也就是爲何lodash的uniq函數要規定最大的可迭代數組長度,超過長度採用Set去重法。避免性能浪費緩存
ES6的出現真的很大程度上方便代碼的編寫,ES6這麼方便爲何我還喜歡lodash這種庫,既臃腫複雜,又須要記憶不少API。個人回答是高效、優雅、省心。工做時使用成熟庫是對代碼質量的保證,而且成熟的庫通常會對性能部分進行優化。性能優化