新的ES 6 (Harmony)引入了新的Set对象。Set使用的恒等算法类似于===运算符,所以不太适合比较对象:

var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]

如何自定义相等的集合对象,以做深度对象比较?有没有类似Java = (Object)的东西?


当前回答

正如其他人所说,当前版本的Set没有办法做到这一点。 我的建议是使用数组和映射的组合。

下面的代码将基于您自己定义的键创建唯一键的映射,然后将唯一项的映射转换为一个数组。

Const数组= [ {"name": "Joe", "age": 17}, {"name": "Bob", "age": 17}, {"name": "Carl", "age": 35} ] Const key = 'age'; const arrayUniqueByKey =[…]新地图(数组。地图(项= > (项目(关键),项目))). values ()]; console.log (arrayUniqueByKey); / *输出 [ {"name": "Bob", "age": 17}, {"name": "Carl", "age": 35} ] * / //注意:这将选择列表中最后一个重复的项。

其他回答

更新3/2022

目前有一个提议将记录和元组(基本上是不可变的对象和数组)添加到Javascript中。在这个提议中,它提供了使用===或!==来比较值的记录和元组的直接比较,而不仅仅是对象引用,并且与这个答案相关的Set和Map对象都将在键比较/查找中使用记录或元组的值,这将解决这里所要求的问题。

由于记录和元组是不可变的(不能修改),并且因为它们很容易通过值(通过它们的内容,而不仅仅是它们的对象引用)进行比较,它允许map和set使用对象内容作为键,拟议的规范明确地为Sets和map命名了这一特性。

这个最初的问题要求Set比较的可定制性,以支持深度对象比较。这并没有提出Set比较的可定制性,但如果您使用新的Record或Tuple而不是object或Array,则它直接支持深度对象比较,从而解决了这里的原始问题。

请注意,该提案于2021年年中推进到第二阶段。最近一直在向前推进,但肯定还没有完成。

Mozilla在这个新提议上的工作可以在这里找到。


原来的答案

ES6 Set对象没有任何比较方法或自定义比较扩展性。

.has(), .add()和.delete()方法只能在原语是相同的实际对象或相同的值时起作用,而不能插入或替换该逻辑。

您可以假定从Set中派生出自己的对象,并将.has()、.add()和.delete()方法替换为首先进行深度对象比较的方法,以查找该项是否已经在Set中,但性能可能不会很好,因为底层Set对象根本没有帮助。在调用原始的.add()之前,您可能必须对所有现有对象进行蛮力迭代,使用您自己的自定义比较来查找匹配。

以下是本文和ES6特性讨论中的一些信息:

5.2 Why can’t I configure how maps and sets compare keys and values? Question: It would be nice if there were a way to configure what map keys and what set elements are considered equal. Why isn’t there? Answer: That feature has been postponed, as it is difficult to implement properly and efficiently. One option is to hand callbacks to collections that specify equality. Another option, available in Java, is to specify equality via a method that object implement (equals() in Java). However, this approach is problematic for mutable objects: In general, if an object changes, its “location” inside a collection has to change, as well. But that’s not what happens in Java. JavaScript will probably go the safer route of only enabling comparison by value for special immutable objects (so-called value objects). Comparison by value means that two values are considered equal if their contents are equal. Primitive values are compared by value in JavaScript.

对那些在谷歌上发现这个问题的人(像我一样)想要使用对象作为键来获得一个Map的值:

警告:此答案不适用于所有对象

var map = new Map<string,string>();

map.set(JSON.stringify({"A":2} /*string of object as key*/), "Worked");

console.log(map.get(JSON.stringify({"A":2}))||"Not worked");

输出:

工作

正如在jfriend00的回答中提到的,平等关系的定制可能是不可能的。

下面的代码给出了一个计算效率高(但内存消耗大)的解决方案:

class GeneralSet {

    constructor() {
        this.map = new Map();
        this[Symbol.iterator] = this.values;
    }

    add(item) {
        this.map.set(item.toIdString(), item);
    }

    values() {
        return this.map.values();
    }

    delete(item) {
        return this.map.delete(item.toIdString());
    }

    // ...
}

每个插入的元素都必须实现返回字符串的toIdString()方法。当且仅当两个对象的toIdString方法返回相同的值时,才认为它们相等。

为了补充这里的答案,我实现了一个Map包装器,它接受一个自定义哈希函数、一个自定义相等函数,并将具有等效(自定义)哈希值的不同值存储在存储桶中。

可以预见的是,它比czerny的字符串连接方法要慢。

完整源代码在这里:https://github.com/makoConstruct/ValueMap

正如其他人所说,目前还没有本地方法可以做到这一点。 但是如果你想用你的自定义比较器来区分一个数组,你可以尝试用reduce方法来做。

function distinct(array, equal) {
  // No need to convert it to a Set object since it may give you a wrong signal that the set can work with your objects.
  return array.reduce((p, c) => {
    p.findIndex((element) => equal(element, c)) > -1 || p.push(c);
    return p;
  }, []);
}

// You can call this method like below,
const users = distinct(
    [
      {id: 1, name: "kevin"},
      {id: 2, name: "sean"},
      {id: 1, name: "jerry"}
    ],
    (a, b) => a.id === b.id
);
...