lodash源碼分析之去重--uniq方法


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源碼😂

	// 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.注意下面的代碼:

	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執行層去做循環,也是一種性能優化。

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。我的回答是高效、優雅、省心。工作時使用成熟庫是對代碼質量的保證,並且成熟的庫一般會對性能部分進行優化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM