数组去重的几种姿势

上篇文章说到了引导式访问组件,其中有个扩展功能是是否强制以及是否第一次进行引导访问,这时候有个 guideKey 可以作为根据判断,那么存储拿取的时候就用到唯一值了,然后就有了这篇文章介绍的几种姿势,有深入哦~


背景

假设已经使用 guideKeyList 来记录已完成的引导步骤:

js 复制代码
let guideKeyList = uni.getStorageSync("guideKeyList") || [];
guideKeyList.push(this.guideKey);
guideKeyList = guideKeyList.unique(); // 自定义 unique 方法
uni.setStorageSync("guideKeyList", guideKeyList);

姿势一:原始写法(for 循环 + includes)

js 复制代码
Array.prototype.unique = function () {
  let arr = [];
  for (let i = 0; i < this.length; i++) {
    if (!arr.includes(this[i])) {
      arr.push(this[i]);
    }
  }
  return arr;
};

优点:直观、好理解。

缺点:性能差(includes() 是 O(n))、代码冗长。


姿势二:原型扩展优化(使用 Object.create(null)

js 复制代码
Array.prototype.unique = function () {
  const seen = Object.create(null);
  const result = [];

  for (let i = 0; i < this.length; i++) {
    const item = this[i];
    if (!seen[item + typeof item]) {
      seen[item + typeof item] = true;
      result.push(item);
    }
  }

  return result;
};

优点:性能比 includes() 更优,避免 key 冲突。

缺点:污染 Array.prototype,多人协作项目慎用。

建议在文档中说明使用原型扩展的地方,防止冲突。


姿势三:推荐方式(使用 Set 封装函数)

js 复制代码
function uniqueArray(arr) {
  return [...new Set(arr)];
}

优点:性能优秀,语义简洁,无副作用

缺点:IE 不支持 Set(已不再重要)。

如果不想污染原型链,这是最推荐的方式。


姿势四:filter + indexOf

js 复制代码
function uniqueArray(arr) {
  return arr.filter((v, i, a) => a.indexOf(v) === i);
}

优点:不污染原型,兼容性好。

缺点:性能比 Set 略差,代码略冗余。


最终整合

在引导结束下使用:

js 复制代码
finish() {
  // 可以单独拎出来在 main.ts or App.vue 等直接先实现一波,比较好看点,容易维护
  Array.prototype.unique = function () {
    const seen = Object.create(null);
    const result = [];

    for (let i = 0; i < this.length; i++) {
      const item = this[i];
      if (!seen[item + typeof item]) {
        seen[item + typeof item] = true;
        result.push(item);
      }
    }
    return result;
  };

  this.visible = false
  let guideKeyList = uni.getStorageSync('guideKeyList') || []
  guideKeyList.push(this.guideKey)
  guideKeyList = guideKeyList.unique() // 也可以替换为 Array.from(new Set(...))
  uni.setStorageSync('guideKeyList', guideKeyList)
  this.$emit('finish')
}

总结

方法 是否污染原型 性能 可读性 兼容性
for + includes ✅ 是 ❌ 差 ✅ 简单 ✅ 高
Object.create ✅ 是 ✅ 中 ✅ 清晰 ✅ 高
Set ❌ 否 ✅ 高 ✅ 极简 ❌ 旧 IE 不支持
filter+indexOf ❌ 否 ✅ 中 ✅ 普通 ✅ 高

如果是项目封装库或者多人协作,避免扩展原型链 ,推荐使用函数封装(如 uniqueArray(arr))。


拓展姿势:对象数组去重 & 深度去重

对象数组去重(根据 id 去重):

js 复制代码
function uniqueByKey(arr, key) {
  const seen = new Set();
  return arr.filter((item) => {
    const val = item[key];
    if (seen.has(val)) return false;
    seen.add(val);
    return true;
  });
}

// 示例
const arr = [
  { id: 1, name: "A" },
  { id: 2, name: "B" },
  { id: 1, name: "C" },
];

console.log(uniqueByKey(arr, "id"));
// => [ { id: 1, name: 'A' }, { id: 2, name: 'B' } ]

深度去重(针对嵌套对象结构):

js 复制代码
function deepUnique(arr) {
  const seen = new Set();
  return arr.filter((item) => {
    const str = JSON.stringify(item);
    if (seen.has(str)) return false;
    seen.add(str);
    return true;
  });
}

// 示例:
const nestedArr = [
  { id: 1, data: { x: 1 } },
  { id: 2, data: { x: 2 } },
  { id: 1, data: { x: 1 } },
];

console.log(deepUnique(nestedArr));

注意:deepUnique 的比较是基于 JSON 字符串的浅层一致性,不适用于包含函数或 undefined 的复杂对象。


更进一步由大佬们来把姿势实现下吧:

  • 实现可配置的 unique(arr, { deep: true, key: 'id' }) 工具函数
  • 集成 lodash 或 Ramda 实现更强大的数据操作链

欢迎评论区继续探讨!

相关推荐
骑驴看星星a26 分钟前
Vue中的scoped属性
前端·javascript·vue.js
四月_h33 分钟前
在 Vue 3 + TypeScript 项目中实现主题切换功能
前端·vue.js·typescript
qq_4275060839 分钟前
vue3写一个简单的时间轴组件
前端·javascript·vue.js
雨枪幻。2 小时前
spring boot开发:一些基础知识
开发语言·前端·javascript
lecepin2 小时前
AI Coding 资讯 2025.8.27
前端·ai编程
TimelessHaze3 小时前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
执键行天涯3 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
青青子衿越3 小时前
微信小程序web-view嵌套H5,小程序与H5通信
前端·微信小程序·小程序
OpenTiny社区3 小时前
TinyEngine 2.8版本正式发布:AI能力、区块管理、Docker部署一键强化,迈向智能时代!
前端·vue.js·低代码
qfZYG4 小时前
Trae 编辑器在 Python 环境缺少 Pylance,怎么解决
前端·vue.js·编辑器