从数组中删除重复的对象

455 阅读2分钟

这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战

假设我们有一系列对象,例如:

const user = [
  {
    name: 'Lay',  
    age: 19
  },
  {
    name: 'IU',  
    age: 18
  },
  {
    name: 'D.O',  
    age: 17
  },
  {
    name: 'Lay',  
    age: 19
  },
  {
    name: 'D.O',  
    age: 17
  },
  {
    name: 'D.O',  
    age: 17
  }
]

我们想要从数组中删除这些重复的对象,我们该如何解决?

为了理解原因,让我们先来看看如何从平面项数组(如字符串)中删除重复项。

从数组中删除重复的平面项

假设我们有一个字符串数组,如下所示:

const str = [
  'javascript', 
  'css', 
  'javascript'
]

如果我们想从这个数组中删除任何重复项,我们可以使用 Array.prototype.filter()String.prototype.indexOf() 方法来检查任何给定项是否是重复项。

// 如果当前项的索引与该项第一次出现的索引相同,则返回到新数组
const filteredStr = str.filter((item, index) => str.indexOf(item) === index)

对象的工作方式不相同

这种方法不适用于对象的原因是,任何两个具有相同属性和值的对象实际上并不被认为是相同的。

const user1 = {
  name: 'Lay',  
  age: 19
}

const user2 = {
  name: 'Lay',  
  age: 19
}

user1 === user2 // false

具有 nameage 相同,但进行比较时确为 false, 这是因为对象是基于引用而不是结构进行比较的。在比较这两个对象时,不考虑这两个对象具有相同的属性和值。因此,在一个对象数组中,即使存在另一个具有完全相同的属性和值的对象,也总是返回所传递对象的索引。

解决方案

检查两个对象是否具有相同的属性和值的唯一方法是实际检查每个对象的属性和值。

以下该解决方案涉及到执行这个手动检查,但是有一些增强来提高性能和减少不必要的嵌套循环。

  • 仅检查数组中的每一项与其后的每一项,以避免多次比较同一对象
  • 仅检查未发现与任何其他项目重复的项目
  • 在检查每个属性的值是否相同之前,请检查两个对象是否具有相同的键
function removeDuplicates (arr) {
  const result = []
  const duplicatesIndices = []

  // 循环遍历原始数组中的每个项
  arr.forEach((current, index) => {
    if (duplicatesIndices.includes(index)) return

    result.push(current)

    // 循环遍历数组中当前项之后的其他项
    for (
      let comparisonIndex = index + 1
      comparisonIndex < arr.length
      comparisonIndex++
    ) {
      const comparison = arr[comparisonIndex]
      const currentKeys = Object.keys(current)
      const comparisonKeys = Object.keys(comparison)

      // 检查对象中的键数
      if (currentKeys.length !== comparisonKeys.length) continue

      // 检查关键字名称
      const currentKeysString = currentKeys.sort().join('').toLowerCase()
      const comparisonKeysString = comparisonKeys.sort().join('').toLowerCase()
      if (currentKeysString !== comparisonKeysString) continue

      // 检查值
      let valuesEqual = true
      for (let i = 0; i < currentKeys.length; i++) {
        const key = currentKeys[i]
        if (current[key] !== comparison[key]) {
          valuesEqual = false
          break
        }
      }
      if (valuesEqual) duplicatesIndices.push(comparisonIndex)
    }
  })

  return result
}