浅谈js深度去重的实现

283 阅读5分钟

引言

在 JavaScript 开发过程中,我们经常会遇到数组去重的需求。对于简单的基本类型数据,小管曾经写过一篇文章详解(点击跳转), 但是最近小管又遇到问题,那就是对于包含对象的数组,普通的去重方法仅能去除引用相同的对象,而不能识别内容相同但引用不同的对象。

引用地址:在 JavaScript 里,当你创建一个对象时,内存会为这个对象分配一块空间来存储它的属性和方法。而变量存储的并非对象的实际内容,而是指向该对象所在内存空间的引用地址

所以,当包含对象的数组包含两个内容相同的对象时,普通数组去重的方法判断这两个内容相同的对象引用地址不相同。因此,我们需要自定义去重逻辑,以实现深度去重。

代码实现

1. 原始数据

我们先给出一个包含对象的数组,请注意,第一条和第五条数据内容一模一样,但是他们的引用地址不相同

let arr = [
    { name: 'John', age: 25 ,like:{firstName: 'John', lastName:'a'}},
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 28 },
    { name: 'Charlie', age: 27 },
    { name: 'John', age: 25 ,like:{firstName: 'John', lastName:'a'}},
];

2. 去重函数

下面我们先写一个去重函数 unique

function unique(arr){
    let res = []
    return res
}

  • 定义一个函数传入一个数组arr,我们希望它返回一个去重后的数组res
 function unique(arr){
    let res = []
    for(let i = 0; i < arr.length; i++){
        let isFind = false
        for(let j = 0; j < res.length; j++){
            
        }
    }
    return res
}
  • 定义两个for循环,第一层for循环拿到原始数组arr中的值后,拿到这个值和,第二层for循环遍历到的res中所有值相比。且在第一层循环设置一个开关变量isFind 用于标记当前元素是否已经存在于结果数组 res 中
function unique(arr){
    let res = []
    for(let i = 0; i < arr.length; i++){
        let isFind = false
        for(let j = 0; j < res.length; j++){
            if(equal(arr[i], res[j])){
               let isFind = true
               break 
            }
        } 
    }
    return res
}
  • 每拿到一个arr[i],我们都遍历结果数组 res 中的每一个元素此时我们希望有一个函数能比较arr[i], res[j]两个数的值,如果这两个数相同,开关变量isFind标记为true,立即跳出res的循环,接着取到下一个arr中的值,进行下一次循环
function unique(arr){
    let res = []
    for(let i = 0; i < arr.length; i++){
        isFind = false
        for(let j = 0; j < res.length; j++){
            if(equal(arr[i], res[j])){
                isFind = true
                break 
            }
        } 
        if(!isFind){
            res.push(arr[i])
        }
    }
    return res
}

  • 当res循环结束,而isFind还为false时,则说明,该arr中的元素不存在于res中, 将该元素添加到结果数组 res 中。

3. 深度比较函数 equal

上面的去重函数中我们用到了equal函数比较两个元素是否相同,下面我们一起来写出这个深度比较函数

类型判断

先定义一个函数,并传入两个值v1 v2,先判断他们两个的类型 ,如果两个都为对象,则进行深度比较,如果不是,则直接返回v1===v2的比较结果

function equal(v1, v2){
    if((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)){
    } else{
        return v1===v2
    }
}
比较两个对象key的长度

已知Object.keys(obj),会返回一个数组,里边包含着对象obj的所有key,运用Object.keys(obj).length则可以得到objkey值的长度。

如果Object.keys(v1).lengthObject.keys(v2).length不相等,直接不用比内容了,返回flase

function equal(v1, v2){
    if((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)){
        if(Object.keys(v1).length === Object.keys(v2).length){

        }else{
            return false
        }
    } else{
        return v1===v2
    }
}
key的存在性和值的比较

遍历 v1 的所有key,对于每个key,检查它是否存在于 v2 中。如果存在,再次比较key所对应的值,等等,又是比较值,没错,就是递归

想回顾以下递归知识或者对递归不太熟悉的朋友可以看看小管的递归详解文章:juejin.cn/post/745004…

递归调用 equal 函数比较对应键的值。如果对应键的值不相等,说明两个对象不相等,返回 false。如果键不存在于 v2 中,同样说明两个对象不相等,返回 false

function equal(v1, v2){
    if((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)){
        if(Object.keys(v1).length === Object.keys(v2).length){
            for(let key in v1){
                if(key in v2){
                    if(!equal(v1[key], v2[key])){
                        return false
                    }
                }
            }
        }else{
            return false
        }
    } else{
        return v1===v2
    }
}
返回结果

如果经过上述所有检查,都没有发现不相等的情况,说明两个对象的所有键和对应的值都相等,回到第一个for的外层空间,返回 true

function equal(v1, v2){
    if((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)){
        if(Object.keys(v1).length === Object.keys(v2).length){
            for(let key in v1){
                if(key in v2){
                    if(!equal(v1[key], v2[key])){
                        return false
                    }
                }
            }
            return true
        }else{
            return false
        }
    } else{
        return v1===v2
    }
}

4.运行代码,检查结果

完整代码如下

let arr = [
    { name: 'John', age: 25 ,like:{firstName: 'John', lastName:'a'}},
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 28 },
    { name: 'Charlie', age: 27 },
    { name: 'John', age: 25 ,like:{firstName: 'John', lastName:'a'}},
]

function unique(arr){
    let res = []
    for(let i = 0; i < arr.length; i++){
        isFind = false
        for(let j = 0; j < res.length; j++){
            if(equal(arr[i], res[j])){
                isFind = true
                break 
            }
        } 
        if(!isFind){
            res.push(arr[i])
        }
    }
    return res
}

function equal(v1, v2){
    if((typeof v1 === 'object' && v1 !== null) && (typeof v2 === 'object' && v2 !== null)){
        if(Object.keys(v1).length === Object.keys(v2).length){
            for(let key in v1){
                if(key in v2){
                    if(!equal(v1[key], v2[key])){
                        return false
                    }
                }
            }
            return true
        }else{
            return false
        }
    } else{
        return v1===v2
    }
}

console.log(unique(arr));

此时运行,将打印处如下结果

[
    { name: 'John', age: 25 ,like:{firstName: 'John', lastName:'a'}},
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 28 },
    { name: 'Charlie', age: 27 },
]

大功告成!很好,js数组去重的所有内容都在这儿了。曾经我在上一篇写数组去重的文章说,看那篇文章就够了,看来代码的学习是永无止境的啊,继续努力学习吧,加油!

p683781402.jpg