数组中的数据根据key去重

实现一个函数,对数组中的对象根据指定的key进行去重,保留第一个或最后一个出现的元素

问题

在实际开发中,我们经常需要对对象数组进行去重操作。与基本类型数组去重不同,对象数组需要根据对象的某个属性(key)来判断是否重复。例如,一个用户列表中可能存在多个相同 id 的用户数据,我们需要根据 id 进行去重,只保留其中一条记录。

解答

/**
 * 根据指定的key对对象数组进行去重
 * @param {Array} arr - 需要去重的数组
 * @param {String} key - 用于判断重复的属性名
 * @param {Boolean} keepLast - 是否保留最后一个,默认保留第一个
 * @returns {Array} 去重后的新数组
 */
function uniqueByKey(arr, key, keepLast = false) {
  // 方法1: 使用 Map(推荐)
  const map = new Map();
  
  if (keepLast) {
    // 保留最后一个:正序遍历,后面的会覆盖前面的
    arr.forEach(item => {
      map.set(item[key], item);
    });
  } else {
    // 保留第一个:正序遍历,只在不存在时添加
    arr.forEach(item => {
      if (!map.has(item[key])) {
        map.set(item[key], item);
      }
    });
  }
  
  return Array.from(map.values());
}

/**
 * 方法2: 使用 reduce
 */
function uniqueByKeyReduce(arr, key, keepLast = false) {
  const map = arr.reduce((acc, item) => {
    if (keepLast || !acc.has(item[key])) {
      acc.set(item[key], item);
    }
    return acc;
  }, new Map());
  
  return Array.from(map.values());
}

/**
 * 方法3: 使用 filter + findIndex
 * 注意:此方法性能较差,时间复杂度 O(n²)
 */
function uniqueByKeyFilter(arr, key, keepLast = false) {
  return arr.filter((item, index) => {
    const findIndex = keepLast 
      ? arr.findLastIndex(obj => obj[key] === item[key])
      : arr.findIndex(obj => obj[key] === item[key]);
    return findIndex === index;
  });
}

/**
 * 方法4: 支持多个key去重
 */
function uniqueByKeys(arr, keys) {
  const map = new Map();
  
  arr.forEach(item => {
    // 将多个key的值组合成唯一标识
    const uniqueKey = keys.map(k => item[k]).join('|');
    if (!map.has(uniqueKey)) {
      map.set(uniqueKey, item);
    }
  });
  
  return Array.from(map.values());
}

使用示例

// 示例数据
const users = [
  { id: 1, name: '张三', age: 25 },
  { id: 2, name: '李四', age: 30 },
  { id: 1, name: '张三(重复)', age: 26 },
  { id: 3, name: '王五', age: 28 },
  { id: 2, name: '李四(重复)', age: 31 }
];

// 1. 根据 id 去重,保留第一个
console.log(uniqueByKey(users, 'id'));
// 输出: [
//   { id: 1, name: '张三', age: 25 },
//   { id: 2, name: '李四', age: 30 },
//   { id: 3, name: '王五', age: 28 }
// ]

// 2. 根据 id 去重,保留最后一个
console.log(uniqueByKey(users, 'id', true));
// 输出: [
//   { id: 1, name: '张三(重复)', age: 26 },
//   { id: 2, name: '李四(重复)', age: 31 },
//   { id: 3, name: '王五', age: 28 }
// ]

// 3. 根据 name 去重
console.log(uniqueByKey(users, 'name'));

// 4. 多个key组合去重
const products = [
  { category: 'A', type: 'X', price: 100 },
  { category: 'A', type: 'Y', price: 200 },
  { category: 'A', type: 'X', price: 150 }, // 重复
  { category: 'B', type: 'X', price: 300 }
];

console.log(uniqueByKeys(products, ['category', 'type']));
// 输出: [
//   { category: 'A', type: 'X', price: 100 },
//   { category: 'A', type: 'Y', price: 200 },
//   { category: 'B', type: 'X', price: 300 }
// ]

关键点

  • 使用 Map 数据结构:Map 的 key 可以是任意类型,且能保持插入顺序,非常适合去重场景,时间复杂度为 O(n)

  • 保留策略:通过 keepLast 参数控制保留第一个还是最后一个重复项,灵活应对不同业务需求

  • 性能考虑:推荐使用 Map 方案,避免使用 filter + findIndex 的嵌套循环方式(O(n²)复杂度)

  • 多key去重:当需要根据多个属性组合判断唯一性时,可以将多个 key 的值拼接成字符串作为 Map 的键

  • 不可变性:所有方法都返回新数组,不修改原数组,符合函数式编程思想

  • 边界处理:需要考虑 key 不存在、值为 undefined/null 等边界情况,可根据实际需求添加校验逻辑