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。我的回答是高效、优雅、省心。工作时使用成熟库是对代码质量的保证,并且成熟的库一般会对性能部分进行优化。

原文地址:https://www.cnblogs.com/mufeng3421/p/10262636.html

时间: 2024-10-08 00:48:42

lodash源码分析之去重--uniq方法的相关文章

lodash源码分析之缓存使用方式的进一步封装

在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbook也会同步仓库的更新,gitbook地址:pocket-lodash 前言 在之前的<lodash源码分析之Hash缓存>和<lodash源码分析之List缓存>介绍过 lodash 的两种缓存方式,在<lodash源码分析之缓存方式的选择&g

cocos2d-x 源码分析 之 CCTableView源码分析(附使用方法讨论)

cocos2d-x源码总目录 http://blog.csdn.net/u011225840/article/details/31743129 源码来自2.x,转载请注明 1.继承结构 首先来看下CCTableView的继承结构 从继承结构上看,CCTableView是一种CCScrollView,所以为了研究CCTableView的源码,清先去了解CCScrollView的源码http://blog.csdn.net/u011225840/article/details/30033501. 其

lodash源码分析之compact中的遍历

小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅浅的海峡, 我在这头, 大陆在那头. --余光中<乡愁> 本文为读 lodash 源码的第三篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbook也会同步仓库的更新,gitbook地址:pocket-lodash 作用与用法 compact 函数用来去除数组中的

monkey源码分析之事件注入方法变化

在上一篇文章<Monkey源码分析之事件注入>中,我们看到了monkey在注入事件的时候用到了<Monkey源码分析番外篇之Android注入事件的三种方法比较>中的第一种方法,通过Internal API的WindowManager的injectKeyEvent之类的方法注入事件.这种方法在android api level 16也就是android4.1.2之后已经发生了变化: 在此之后注入事件的方式变成了使用InputManager的injectInputEvent方法了 而

lodash源码分析之Hash缓存

在那小小的梦的暖阁,我为你收藏起整个季节的烟雨. --洛夫<灵河> 本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbook也会同步仓库的更新,gitbook地址:pocket-lodash 作用与用法 Hash 顾名思义,就是要有一个离散的序列,根据 key 来储取数据.而在 javascript 中,最适合的无疑是对象了. Hash 在 lodash 的 .internal 文件夹中,作为内部文件来使用.lodash 会根

Thrift源码分析(四)-- 方法调用模型分析

RPC调用本质上就是一种网络编程,客户端向服务器发送消息,服务器拿到消息之后做后续动作.只是RPC这种消息比较特殊,它封装了方法调用,包括方法名,方法参数.服务端拿到这个消息之后,解码消息,然后要通过方法调用模型来完成实际服务器端业务方法的调用. 这篇讲讲Thrfit的方法调用模型.Thrift的方法调用模型很简单,就是通过方法名和实际方法实现类的注册完成,没有使用反射机制,类加载机制. 和方法调用相关的几个核心类: 1. 自动生成的Iface接口,是远程方法的顶层接口 2. 自动生成的Proc

【Seajs源码分析】3. 工具方法2

util-request.js 动态加载模块 /** * util-request.js - The utilities for requesting script and style files * ref: tests/research/load-js-css/test.html */ var head = doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement var baseElement =

【Seajs源码分析】2. 工具方法1

Sea.js: var seajs = global.seajs = { // The current version of Sea.js being used version: "@VERSION" } var data = seajs.data = {} 代码定义了一个seajs变量并暴露给全局,变量现在只有一个值就是版本号变量 另外定义了一个data变量,后面会用到 util-lang.js /** * util-lang.js - The minimal language en

RxJava1.0 flatMap方法的源码分析

RxJava1.0 flatMap方法的源码分析 package com.yue.test; import java.awt.Cursor; import java.util.ArrayList; import java.util.List; import com.yue.bean.Course; import com.yue.bean.Student; import rx.Observable; import rx.Subscription; import rx.Observable.OnSu