首页 > 编程语言 >全网最全情景,深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略

全网最全情景,深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略

时间:2024-12-04 11:28:33浏览次数:5  
标签:深入浅出 obj name 最全 JavaScript Alice let 数组 id

目录

全网最全情景,深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略

一、引言:我们为什么需要关注数组去重?

二、数值类去重

1、使用 Set 去重

2、遍历 + includes()

3、使用 filter() 和 indexOf()

4、使用 reduce()

5、嵌套数组去重:结合 flat()

三、引用类去重——去除完全重复的对象元素

1、JSON.stringify() + Set

2、使用 Map() 方法

四、特殊情况:对象的键值对可能顺序不同,但其内容相同

1、自定义函数比较对象内容

2、标准化对象的键顺序

3、使用 Map 存储标准化后的对象

五、引用类去重——去除部分重复的对象元素

1、保留每个 id 对应的最后一个对象

2、根据多字段组合去重

3、根据特定规则保留对象:保留最高分的学生记录

六、混合数组去重

七、对比与总结

1、使用场景对比

2、总结


作者:watermelo37

涉及领域:Vue、SpingBoot、Docker、LLM、python等

---------------------------------------------------------------------

温柔地对待温柔的人,包容的三观就是最大的温柔。

---------------------------------------------------------------------

深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略

一、引言:我们为什么需要关注数组去重?

        在日常开发中,数组去重是一个不可避免的话题。不管是简单的数值数组去重,还是复杂的引用类型数组去重,掌握多种方法可以帮助开发者高效、优雅地解决实际问题。在这篇博客中,我们将从基础到进阶,结合大量代码案例,系统介绍数组去重的各种技巧。

前排提醒:

        本文主要讨论的去重情况包括:

        1、数值类去重,数组的元素往往是基础数据类型,这种情况相对容易,有很多种方法可以做到。

        2、引用类去重,数组的元素往往是对象、数组甚至多类型混合。这种情况下的去重会复杂很多,并且还可以分为去除完全重复和部分重复两种。

        ①去除完全重复就是两个引用类型的元素完全一致,其中还包括元素内容一致但键值对顺序不一致的特殊情况。

        ②去除部分重复就比如两个元素的name一致,就要去除其中的一个,并根据某种规则留下一个特定的元素。

二、数值类去重

1、使用 Set 去重

        Set 是去重的“万金油”,可以自动移除重复的元素,适合大多数基础类型数组去重。简洁高效,推荐使用。但仅适合基础数据类型。

let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]

2、遍历 + includes()

        对于初学者,遍历结合 includes() 是一种直观的方法,易于理解,适合新手学习,但对大数组性能较低。初学者做开发学习时候遇不到大数组大数据,所以这个方法是完全可行的。

let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = [];
for (let num of numbers) {
    if (!uniqueNumbers.includes(num)) {
        uniqueNumbers.push(num);
    }
}
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]

3、使用 filter() 和 indexOf()

        filter() 和 indexOf() 是函数式编程的经典组合,用于去重非常直观,写法简洁,可作为 Set 的替代方案,但是indexOf() 遍历时间复杂度较高。

let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = numbers.filter((num, index) => numbers.indexOf(num) === index);
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]

4、使用 reduce()

        reduce() 可以通过累积器动态生成去重数组,灵活且功能强大,灵活性强,可结合复杂逻辑处理,但代码相对复杂。

        这里使用reduce()核心是利用它的累加器,累加器不只可以用来累加,可以用来做任何事情,包括特殊情况下作为forEach()和map()的替代。

let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = numbers.reduce((acc, num) => {
    if (!acc.includes(num)) {
        acc.push(num);
    }
    return acc;
}, []);
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]

5、嵌套数组去重:结合 flat()

        如果数组中有嵌套的情况,可以配合 flat() 进行去重。

let nestedNumbers = [1, [2, 2], [3, 4, 4], 5];
let uniqueNumbers = [...new Set(nestedNumbers.flat())];
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]

三、引用类去重——去除完全重复的对象元素

        引用类去重的场景更为复杂,因为对象和数组属于引用类型,哪怕键值完全相同也会分属于不同的引用地址,直接比较无法判断重复性。我们会从简单到复杂,详细介绍解决方案。

        完全重复是指两个对象的键值对完全相同。

1、JSON.stringify() + Set

        将对象转换为字符串表示,再利用 Set 去重,这样代码简洁,适合结构简单的对象数组,但是对嵌套对象或顺序无关的对象有局限性(比如某个相同元素name在前,id在后,这样就无法去重了)。

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 1, name: "Alice" }
];

let uniqueObjects = Array.from(new Set(objects.map(JSON.stringify))).map(JSON.parse);

console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

2、使用 Map() 方法

        通过 Map 的键值对特性保存唯一对象,适合复杂数据结构,性能优于 Set。

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 1, name: "Alice" }
];

let uniqueObjects = Array.from(new Map(objects.map(obj => [JSON.stringify(obj), obj])).values());

console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

Tips:

        在JavaScript中,Map 和 Set 是两种不同的集合类型,它们都是ES6中引入的,用于存储和管理一系列值。它们之间有几个关键的区别:

Map 对象

  • Map 对象保存键值对,并且能够记住键的原始插入顺序。
  • 任何值(对象或者原始值)都可以作为一个键或一个值。
  • Map 对象提供了许多实用的方法,如 set、get、has 和 delete,来操作映射。
  • Map 对象是可迭代的,这意味着它们可以用于 for...of 循环。

Set 对象

  • Set 对象只保存唯一的值,即不允许重复。
  • Set 对象同样保存元素的插入顺序。
  • Set 对象提供了 add、has 和 delete 等方法来操作集合。
  • Set 对象也是可迭代的,适用于 for...of 循环。

        一言以蔽之,一个是键值对集合,一个是数值集合。

四、特殊情况:对象的键值对可能顺序不同,但其内容相同

        当对象的键值对顺序不同,但其内容相同时,使用 JSON.stringify() 或 Map() 方法会出现问题,因为 JSON.stringify() 会把对象的键值对顺序也纳入到字符串化的过程,而 Map() 是基于键值对的映射,在对象属性的顺序不同的情况下,结果也可能不一致。

        比如这样的数据就是无法去重的:

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { name: "Alice", id: 1 }
];

         为了处理这类键顺序不同但内容相同的对象去重,我们可以采取以下两种方法:

1、自定义函数比较对象内容

        通过自定义函数来统一对象的键的顺序,确保不论顺序如何都能正确去重。

function deepEqual(obj1, obj2) {
    const keys1 = Object.keys(obj1).sort();
    const keys2 = Object.keys(obj2).sort();
    
    if (keys1.length !== keys2.length) {
        return false;
    }

    for (let i = 0; i < keys1.length; i++) {
        const key = keys1[i];
        if (obj1[key] !== obj2[key]) {
            return false;
        }
    }

    return true;
}

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { name: "Alice", id: 1 }
];

let uniqueObjects = [];
for (let obj of objects) {
    if (!uniqueObjects.some(existingObj => deepEqual(existingObj, obj))) {
        uniqueObjects.push(obj);
    }
}

console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

2、标准化对象的键顺序

        另一个方法是将对象的键排序,确保对象在进行去重时顺序一致。可以使用 JSON.stringify() 对每个对象进行处理,但是要先标准化它们的键顺序,然后进行比较。

function sortObjectKeys(obj) {
    const sortedObj = {};
    Object.keys(obj).sort().forEach(key => {
        sortedObj[key] = obj[key];
    });
    return sortedObj;
}

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { name: "Alice", id: 1 }
];

// 将对象的键值对顺序标准化
let uniqueObjects = [...new Set(objects.map(obj => JSON.stringify(sortObjectKeys(obj))))].map(item => JSON.parse(item));

console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

3、使用 Map 存储标准化后的对象

        可以将对象标准化为字符串后,存储在 Map 中以实现去重。

function sortObjectKeys(obj) {
    const sortedObj = {};
    Object.keys(obj).sort().forEach(key => {
        sortedObj[key] = obj[key];
    });
    return sortedObj;
}

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { name: "Alice", id: 1 }
];

let map = new Map();
objects.forEach(obj => {
    map.set(JSON.stringify(sortObjectKeys(obj)), obj);
});

let uniqueObjects = Array.from(map.values());

console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

五、引用类去重——去除部分重复的对象元素

        比如根据id去重、根据name去重,这种情况往往是某些不会重复的字段因为数据库的迁徙、合并或者增删修改导致了重复,除了要进行去重以外,还要根据某种规则留下一个特定的元素(因为两个元素并不完全一致)

function sortObjectKeys(obj) {
    const sortedObj = {};
    Object.keys(obj).sort().forEach(key => {
        sortedObj[key] = obj[key];
    });
    return sortedObj;
}

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { name: "Alice", id: 1 }
];

// 将对象的键值对顺序标准化
let uniqueObjects = [...new Set(objects.map(obj => JSON.stringify(sortObjectKeys(obj))))].map(item => JSON.parse(item));

console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

1、保留每个 id 对应的最后一个对象

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 1, name: "Charlie" }
];

let uniqueById = Array.from(new Map(objects.map(obj => [obj.id, obj])).values());

console.log(uniqueById);
// 输出:[{ id: 2, name: 'Bob' }, { id: 1, name: 'Charlie' }]

2、根据多字段组合去重

        这种往往是数据库迁徙、合并的时候出现的问题,新旧数据混合。

let objects = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 1, name: "Alice", age: 25 },
    { id: 1, name: "Alice", age: 30 }
];

let uniqueByFields = objects.reduce((acc, obj) => {
    if (!acc.some(item => item.id === obj.id && item.name === obj.name)) {
        acc.push(obj);
    }
    return acc;
}, []);

console.log(uniqueByFields);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

3、根据特定规则保留对象:保留最高分的学生记录

        比如在多次测评中,某人获得了不同的成绩,要保留其最高分。

let students = [
    { id: 1, name: "Alice", score: 85 },
    { id: 2, name: "Bob", score: 90 },
    { id: 1, name: "Alice", score: 95 }
];

let highestScoreStudents = Array.from(
    students.reduce((acc, student) => {
        if (!acc.has(student.id) || acc.get(student.id).score < student.score) {
            acc.set(student.id, student);
        }
        return acc;
    }, new Map()).values()
);

console.log(highestScoreStudents);
// 输出:[{ id: 2, name: 'Bob', score: 90 }, { id: 1, name: 'Alice', score: 95 }]

六、混合数组去重

        对于包含基础数据类型和引用类型的混合数组,可以分别处理后合并。

let mixedArray = [1, "a", { id: 1 }, "a", { id: 1 }, 2, 1];

// 分离基础类型和引用类型
let primitives = mixedArray.filter(item => typeof item !== "object");
let objects = mixedArray.filter(item => typeof item === "object");

// 分别去重后合并
let uniqueMixedArray = [
    ...new Set(primitives),
    ...Array.from(new Map(objects.map(obj => [JSON.stringify(obj), obj])).values())
];

console.log(uniqueMixedArray);
// 输出:[1, 'a', 2, { id: 1 }]

七、对比与总结

1、使用场景对比

方法适用场景优点缺点
Set基础类型数组去重简洁高效无法处理引用类型
遍历 + includes基础类型数组去重易理解性能较低
filter() + indexOf()基础类型数组去重通用性能较低
reduce()复杂逻辑处理或混合类型数组去重灵活,可扩展逻辑写法稍复杂
JSON.stringify引用类型数组去重简洁无法处理嵌套或无序字段的对象
Map引用类型数组去重性能较优,适合复杂数据结构写法稍繁琐

2、总结

        如果是基础类型数组,优先选择 Set。

        对于引用类型数组,根据需求选择 Map 或 JSON.stringify()。

        其余情况根据实际需求进行混合调用,就能更好的实现数组去重。

        只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

        其他热门文章,请关注:

        你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

        极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图

        通过array.filter()实现数组的数据筛选、数据清洗和链式调用

        el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

        TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急

        Dockerfile全面指南:从基础到进阶,掌握容器化构建的核心工具

        在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境

        MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver

        JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南

        干货含源码!如何用Java后端操作Docker(命令行篇)

        Idea启动SpringBoot程序报错:Port 8082 was already in use;端口冲突的原理与解决方案

        PDF预览:利用vue3-pdf-app实现前端PDF在线展示

标签:深入浅出,obj,name,最全,JavaScript,Alice,let,数组,id
From: https://blog.csdn.net/RenGJ010617/article/details/143950308

相关文章