【前端】vue数组去重的3种方法
'# 【前端】vue数组去重的3种方法
一、背景与问题
在Vue开发中,数组去重是一个高频操作场景。无论是用户输入数据的处理、API返回数据的清洗,还是组件间数据传递的规范化,都需要对数组进行去重处理。但实际开发中,开发者常遇到以下问题:
- 简单类型数组去重有效,但对象数组去重失败
- 重复元素的识别逻辑不严谨导致遗漏
- 大数据量场景下性能问题
- Vue响应式更新机制导致的去重失效
- 去重逻辑破坏原始数据结构
理解这些本质问题,需要从数据结构原理和Vue响应式系统的底层机制出发。
二、基本原理
1. 去重算法原理
数组去重的核心是识别重复元素,其本质是集合操作。常见实现方式包括:
- Set结构:利用哈希表特性,自动去重
- 双指针法:通过比较元素值进行去重
- 归并法:通过累积器逐步构建唯一集合
2. Vue响应式机制
Vue通过Object.defineProperty(Vue2)或Proxy(Vue3)实现响应式,但存在以下限制:
- 对数组的变异方法(push/pop/shift等)会触发更新
- 直接设置索引(arr[0] = 'new')不会触发更新
- 替换整个数组(arr = [])会触发更新
三、环境准备
# 创建Vue项目
npm create vue@latest项目结构建议:
src/
├── components/
│ └── UniqueList.vue
├── utils/
│ └── arrayUtils.js
├── App.vue
└── main.js四、核心实现
方法一:使用Set结构(适用于简单类型)
// utils/arrayUtils.js
export function uniqueSet(arr) {
return [...new Set(arr)];
}关键点解释:
- Set构造函数自动去重,但仅能处理可哈希类型(字符串、数字、Symbol等)
- 对象数组需转换为字符串处理
方法二:filter+includes组合(适用于小数据量)
export function uniqueFilter(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) === index;
});
}性能分析:
- 时间复杂度O(n²)
- 适合数据量小于1000的场景
- 对象数组需重写比较函数
方法三:reduce+JSON.stringify(适用于对象数组)
export function uniqueReduce(arr) {
return arr.reduce((acc, curr) => {
const key = JSON.stringify(curr);
if (!acc.includes(key)) {
acc.push(curr);
}
return acc;
}, []);
}注意事项:
- 转换为字符串可能导致精度丢失(如数字的1e-16)
- 对象属性顺序不同会导致字符串不同
五、完整案例
1. 实现一个可交互的去重组件
<!-- components/UniqueList.vue -->
<template>
<div>
<input v-model="newItem" placeholder="输入内容" />
<button @click="addItem">添加</button>
<ul>
<li v-for="(item, index) in uniqueList" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import { uniqueSet, uniqueFilter } from '../utils/arrayUtils';
export default {
data() {
return {
newItem: '',
originalList: [],
uniqueList: []
};
},
methods: {
addItem() {
if (this.newItem.trim()) {
this.originalList = [...this.originalList, this.newItem];
this.uniqueList = uniqueSet([...this.originalList]);
this.newItem = '';
}
}
}
};
</script>关键点解析:
- 使用Set方法处理字符串数组
- 通过数组赋值触发Vue响应式更新
- 保留原始数组用于后续处理
2. 对象数组去重示例
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }
];
// 使用reduce方法去重
const uniqueUsers = users.reduce((acc, curr) => {
const key = JSON.stringify(curr);
if (!acc.includes(key)) {
acc.push(curr);
}
return acc;
}, []);
console.log(uniqueUsers.length); // 输出2六、源码解析
Set方法源码实现
function uniqueSet(arr) {
const seen = new Set();
return arr.filter(item => {
const key = typeof item === 'object' && item !== null
? JSON.stringify(item)
: item;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}关键点:
- 处理对象数组时进行字符串转换
- 使用Set保证唯一性
- 保持原数组顺序
Filter方法源码实现
function uniqueFilter(arr) {
return arr.filter((item, index, self) => {
return self.findIndex(el =>
typeof el === 'object' ? JSON.stringify(el) === JSON.stringify(item) : el === item
) === index;
});
}优化点:
- 使用JSON.stringify比较对象
- 保持原数组顺序
- 支持复杂类型比较
七、进阶使用
1. 响应式数组去重
// 使用Vue3的ref
import { ref } from 'vue';
const list = ref([]);
const uniqueList = ref([]);
function add(item) {
list.value = [...list.value, item];
uniqueList.value = uniqueSet([...list.value]);
}2. 带状态的去重
const state = reactive({
list: [],
uniqueList: [],
filter: ''
});
function updateFilter(value) {
state.filter = value;
state.uniqueList = uniqueFilter(state.list.filter(item =>
typeof item === 'string' ? item.includes(value) : true
));
}3. 带排序的去重
function uniqueAndSort(arr, key = 'id', order = 'asc') {
return arr
.reduce((acc, curr) => {
const keyVal = typeof key === 'function' ? key(curr) : curr[key];
if (!acc.some(item =>
typeof key === 'function' ? key(item) === keyVal : item[key] === keyVal
)) {
acc.push(curr);
}
return acc;
}, [])
.sort((a, b) => {
const aVal = typeof key === 'function' ? key(a) : a[key];
const bVal = typeof key === 'function' ? key(b) : b[key];
return order === 'asc' ? aVal - bVal : bVal - aVal;
});
}八、性能与工程实践
1. 性能优化策略
| 场景 | 优化方案 | 效果 |
|---|---|---|
| 小数据量 | filter+includes | 无明显性能损失 |
| 大数据量 | Set结构 | 时间复杂度O(n) |
| 对象数组 | JSON.stringify | 转换成本增加但性能稳定 |
| 频繁操作 | 延迟去重 | 减少DOM更新次数 |
2. 响应式优化
- 使用
computed替代手动去重 - 对数组进行冻结处理
- 使用
v-model双向绑定控制输入
3. 异常处理
try {
const result = uniqueSet([...list.value]);
console.log(result);
} catch (e) {
console.error('去重失败:', e);
// 处理异常情况,如数据格式错误
}九、常见问题与踩坑
1. 常见错误示例
// 错误示例:直接修改数组
this.list[0] = 'new value'; // 不会触发更新解决办法:
// 正确方式:使用变异方法
this.$set(this.list, 0, 'new value');2. 对象数组去重失败
// 错误示例:直接比较对象引用
const arr = [{id:1}, {id:1}];
arr.filter(item => item.id !== 1); // 返回空数组解决办法:
// 正确方式:比较对象内容
const arr = [{id:1}, {id:1}];
arr.filter((item, index) =>
arr.findIndex(el => el.id === item.id) === index
);3. 响应式更新失效
// 错误示例:直接替换数组
this.list = this.list.filter(...); // 触发更新
this.list = [...this.list]; // 触发更新
this.list = uniqueSet(this.list); // 触发更新十、最佳实践
1. 使用场景推荐
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 简单类型数组 | Set | 简洁高效 |
| 小数据量 | filter+includes | 代码清晰 |
| 对象数组 | reduce+JSON.stringify | 保证完整性 |
| 需要排序 | uniqueAndSort | 综合处理 |
| 频繁操作 | computed | 自动优化 |
2. 开发规范建议
- 始终使用
JSON.stringify处理对象数组 - 避免直接修改数组索引
- 对大数据量使用Set结构
- 保持去重逻辑与原始数据解耦
- 在组件销毁时清理相关状态
十一、总结
Vue数组去重的本质是数据结构的集合操作,不同方法适用于不同场景。需要根据数据类型、数据量、业务需求选择合适方案。在开发过程中要特别注意Vue的响应式机制,避免因直接修改数组导致更新失效。对于复杂对象数组,推荐使用JSON.stringify进行转换比较,同时注意处理精度丢失问题。在实际项目中,建议采用computed属性和响应式编程思想,将去重逻辑与组件解耦,提升代码的可维护性。
评论已关闭