
在前端开发中,尤其是在处理从后端获取的数据时,我们经常会遇到需要对数组中的对象进行去重操作的需求。例如,给定一个包含多个对象的数组,我们希望根据某个特定的键(如id、category或示例中的trip_class)来确保最终数组中该键的值是唯一的,即使其他属性不同。这在vuex等状态管理库中,通常通过计算属性(computed properties)来实现,以确保数据的响应式和派生性。
理解问题:reduce与find的常见陷阱
原始问题中展示了一种尝试使用Array.prototype.reduce结合Array.prototype.find进行去重的方法,但未能达到预期效果。让我们分析一下其核心问题所在:
flightsClasses.reduce((acc, obj)=>{
var exist = acc.find((flightClass) => obj.trip_class === flightClass ); // 问题所在
if(!exist){
acc.push(obj);
}
return acc;
},[]);这里的关键在于acc.find((flightClass) => obj.trip_class === flightClass )这一行。在find的回调函数中,flightClass是acc数组中的一个对象(例如 {name: 'john', trip_class: 0, ...}),而obj.trip_class是一个原始值(例如 0、1、2)。将一个原始值与一个对象进行严格相等(===)比较,结果通常会是false,除非flightClass本身就是那个原始值,这在当前上下文中是不可能的。因此,find总是找不到匹配项,导致所有对象都被添加到acc中,最终返回一个未去重的数组。
解决方案:修正find的比较逻辑
要正确地使用find来检查唯一性,我们需要比较对象中对应的键值。将acc.find((flightClass) => obj.trip_class === flightClass )修正为acc.find((flightClass) => obj.trip_class === flightClass.trip_class )即可。
以下是修正后的computed属性实现:
立即学习“前端免费学习笔记(深入)”;
// 假设这是您的Vue组件或Vuex Getter
computed: {
flights() {
// 从Vuex store获取原始航班数据
return this.$store.getters.getFlights;
},
flightsClassesUnique() {
const allFlights = this.flights; // 获取原始数据,避免直接修改
if (!allFlights || allFlights.length === 0) {
return [];
}
// 使用 reduce 方法进行去重
const uniqueFlights = allFlights.reduce((accumulator, currentObject) => {
// 检查累加器中是否已存在具有相同 trip_class 的对象
const exists = accumulator.find(
(itemInAccumulator) => currentObject.trip_class === itemInAccumulator.trip_class
);
// 如果不存在,则将当前对象添加到累加器中
if (!exists) {
accumulator.push(currentObject);
}
return accumulator;
}, []); // 初始累加器为空数组
console.log('uniqueFlights:', uniqueFlights);
return uniqueFlights;
}
}示例数据输入:
[
{name: 'john', trip_class: 0, lastname: 'lastname'},
{name: 'Don', trip_class: 1, lastname: 'lastname'},
{name: 'Joshua', trip_class: 1, lastname: 'lastname'},
{name: 'Mary', trip_class: 2, lastname: 'lastname'}
]期望的输出:
[
{name: 'john', trip_class: 0, lastname: 'lastname'},
{name: 'Don', trip_class: 1, lastname: 'lastname'},
{name: 'Mary', trip_class: 2, lastname: 'lastname'}
]通过上述修正,find方法将正确地比较trip_class的值,从而实现根据该键去重的目的。
其他高效的去重策略
除了reduce与find的组合,还有其他更高效或更简洁的方法来实现对象数组去重,尤其是在处理大型数据集时,性能差异会更加明显。
1. 使用 Map 对象进行去重 (推荐)
Map对象可以存储键值对,并且键可以是任何类型(包括对象引用),但在这里我们利用它的特性来存储唯一的trip_class值。这种方法通常比reduce结合find更高效,因为它避免了每次迭代时对accumulator数组进行线性搜索。
computed: {
flightsClassesUniqueByMap() {
const allFlights = this.flights;
if (!allFlights || allFlights.length === 0) {
return [];
}
const uniqueMap = new Map();
allFlights.forEach(flight => {
// 使用 trip_class 作为 Map 的键,如果键不存在,则添加该对象
if (!uniqueMap.has(flight.trip_class)) {
uniqueMap.set(flight.trip_class, flight);
}
// 或者,如果总是想保留第一次出现的对象,直接 set 即可,Map会自动处理重复键
// uniqueMap.set(flight.trip_class, flight);
});
// 将 Map 的值转换为数组
return Array.from(uniqueMap.values());
}
}说明: Map的键是唯一的。当遇到重复的trip_class时,set操作会覆盖之前的值。如果需要保留第一次出现的对象,则需要先判断!uniqueMap.has(flight.trip_class)。如果无所谓保留哪一个(例如,trip_class相同的对象其他属性也一致),则可以直接uniqueMap.set(flight.trip_class, flight);。
2. 使用 filter 结合 findIndex 或 Map (变种)
这种方法结合了filter的简洁性和findIndex的查找能力,或者同样利用Map来跟踪已见的键。
computed: {
flightsClassesUniqueByFilter() {
const allFlights = this.flights;
if (!allFlights || allFlights.length === 0) {
return [];
}
// 方法一:使用 filter 和 findIndex
// 这种方法在大型数组上性能可能不如 Map,因为它每次都会查找原始数组
return allFlights.filter((flight, index, self) =>
index === self.findIndex((f) => f.trip_class === flight.trip_class)
);
// 方法二:使用 filter 和 Set (如果去重键是原始类型)
// const seen = new Set();
// return allFlights.filter(flight => {
// const duplicate = seen.has(flight.trip_class);
// seen.add(flight.trip_class);
// return !duplicate;
// });
}
}说明:
- filter结合findIndex:对于每个元素,它会检查该元素在数组中第一次出现的索引是否就是当前元素的索引。如果是,则保留;否则,说明之前已经出现过相同trip_class的元素,则过滤掉。这种方法在内部仍有多次遍历,性能相对较低。
- filter结合Set:创建一个Set来存储已经遇到的trip_class值。如果当前元素的trip_class已经在Set中,则跳过;否则,添加到Set并保留该元素。这种方法效率较高。
注意事项与总结
- 数据不可变性: 在Vue/Vuex中,始终推荐使用不可变数据操作。reduce、map、filter等方法都会返回新数组,而不是修改原数组,这符合不可变性原则。避免直接修改this.flights或Vuex state中的原始数组。
-
性能考量:
- 对于小型数组,各种方法的性能差异不明显。
- 对于大型数组(例如,数千甚至数万个对象),使用Map或Set进行去重通常是最高效的选择,因为它们的查找时间复杂度接近O(1)。
- reduce结合find或filter结合findIndex的方案,在最坏情况下(每次find或findIndex都需要遍历大部分已累加/已处理的元素)时间复杂度可能接近O(n^2),应谨慎使用。
- 选择正确的去重键: 确保你用来去重的键(例如trip_class)能够真正代表你想要“唯一”的那个对象。如果两个对象除了trip_class相同外,其他重要属性都不同,你需要决定保留哪一个(通常是第一次出现的那个)。
- 代码可读性: 选择一种既高效又易于理解的方法。Map的解决方案通常被认为是兼顾性能和可读性的良好选择。
通过本文的讲解,您应该能够理解在Vuex中如何正确且高效地根据对象键值获取唯一数组。根据您的具体需求和数据量,选择最适合的去重策略,以构建健壮、高效的Vue应用。










