美文网首页
原生 js 实现对象数组去重

原生 js 实现对象数组去重

作者: HoPGoldy | 来源:发表于2019-12-17 15:14 被阅读0次

    最近研究了一下如何实现对象数组的去重,这里分享一下经验,本文没有使用诸如 lodash 之类的第三方库,仅使用 ES6 中的语法。

    假如有如下数组:

    const originArray = [
        { id: 1, value: 'a' },
        { id: 1, value: 'b' },
        { id: 2, value: 'c' },
        { id: 3, value: 'd' },
        { id: 3, value: 'e' },
        { id: 4, value: 'f' }
    ]
    

    我们要根据其中的元素对象的 id 键进行数组去重,这里我们保留索引靠前的重复元素。

    最终实现

    这里直接给出最终实现,有需要的可以使用:

    /**
     * 根据 id 进行数组去重
     * @param {array} origin 原始数组
     */
    const unique = function (origin) {
        let temp = {}
        return origin.reverse().filter(item => (item.id in temp) ? false : (temp[item.id] = true))    
    }
    

    实现原理

    接下来我们来分析下上面的实现并解释下原理。这种实现方法本质上利用了 对象的键唯一性。通过在临时数组中添加数组的元素 id 作为键来实现数组去重。

    一旦发现 id 已经存在了,就说明该元素是重复元素,直接剔除。如果发现临时数组中没有 id,说明没有重复,就把自己保留下来。

    实现细节

    这个实现里有两个细节需要注意一下:

    首先是我们使用了reverse()对原始数组进行了倒序操作。这个原因是因为 js 中的集合迭代方法是从后往前进行的。也就是说,诸如 .map.filter 等方法都是从数组的最后一个元素开始进行遍历的。而我们的目的是保留重复元素中索引靠前的哪一个,所以需要先进行倒序。如果你没有这个需求或者需要保留索引靠后的重复元素,那么就不需要添加reverse()了。

    第二个细节是后面的三元表达式:

    (item.id in temp) ? false : (temp[item.id] = true)
    

    这里能将判断压缩进一行的原因在于后面的temp[item.id] = true。在 js 里,赋值操作是有返回值的,并且其返回值即为赋值操作的右值这也是为什么 js 里可以连等赋值)。也就是说,a = true 将会返回 true。在这里我们就是使用了temp[item.id] = true的返回值为 true 来实现了保留目标元素的功能。

    性能对比

    接下来比较几种常见的原生对象数组去重的方法,先请出其余两位对比组实现:

    /**
     * 根据 id 进行数组去重 对比1
     * @param {array} origin 原始数组
     */
    function uniqueA(origin) {
        // 临时数组
        let tempObj = { }
        // 倒序后遍历数组进行赋值
        // 以 id 的值为键,以数组元素的索引为值
        // 老的重复元素会因为键重复而被覆盖
        origin.reverse().map((item, index) => tempObj[item.id] = index) 
        // 根据去重后的索引值提取出目标数组
        return Object.values(tempObj).map(index => origin[index])
    }
    
    /**
     * 根据 id 进行数组去重 对比2
     * @param {array} origin 原始数组
     */
    function uniqueB(origin) {
        // 先提取出 id 数组
        return origin.map(item => item.id)
            // 如果该 id 的索引是原数组中第一个该 id 的索引,就返回其索引,否则返回 null
            .map((id, index, arr) => (arr.indexOf(id, 0) === index) ? index : null)
            // 移除上一步产生的 null
            .filter(Boolean)
            // 根据去重后的索引提取出目标数组
            .map(index => origin[index])
    }
    

    接下来增添一些测试所需的辅助函数,下面的代码你可以自行复制进行测试。这里我所用的随机测试数组长度为 10,000,每个方法执行 100 次,统计其总用时。

    // 执行三种方法并返回其用时
    showUniqueUsed(unique)
    showUniqueUsed(uniqueA)
    showUniqueUsed(uniqueB)
    
    /**
     * 显示去重所需时间
     * @param {function} func 进行去重的函数
     * @param {number} time 循环执行多少次
     * @param {number} length 要去重的随机数组长度
     */
    function showUniqueUsed(func, time = 100, length = 10000) {
        console.time('uniqueUsed')
        for (let i = 0; i < time; i ++) {
            func(getRandomArray(length))
        }
        console.timeEnd('uniqueUsed')
    }
    
    /**
     * 获取指定长度的数组,元素形式如下
     * { 
     *     id: 12332, // 0 ~ 指定长度之间的随机整数
     *     value: 12332 // 该元素的索引值
     * }
     * @param {number} length 数组长度
     */
    function getRandomArray(length) {
        return Array(length).fill(null).map((item, index) => ({ 
            id: Math.floor(Math.random() * (length + 1)),
            value: index
        }))
    }
    
    /**
     * 根据 id 进行数组去重 本文实现
     * @param {array} origin 原始数组
     */
    function unique(origin) {
        let temp = {}
        return origin.filter(item => (item.id in temp) ? false : (temp[item.id] = true))    
    }
    
    /**
     * 根据 id 进行数组去重 对比1
     * @param {array} origin 原始数组
     */
    function uniqueA(origin) {
        let tempObj = {}
        origin.reverse().map((item, index) => tempObj[item.id] = index)
        return Object.values(tempObj).map(index => origin[index])
    }
    
    /**
     * 根据 id 进行数组去重 对比2
     * @param {array} origin 原始数组
     */
    function uniqueB(origin) {
        return origin.map(item => item.id)
            .map((id, index, arr) => (arr.indexOf(id, 0) === index) ? index : null)
            .filter(Boolean)
            .map(index => origin[index])
    }
    

    以下是测试结果,可以看到本文中给出的方法用时最短:

    uniqueUsed: 153.780ms
    uniqueUsed: 219.848ms
    uniqueUsed: 4665.961ms
    

    相关文章

      网友评论

          本文标题:原生 js 实现对象数组去重

          本文链接:https://www.haomeiwen.com/subject/wfrsnctx.html