如何为 JavaScript 集自定义对象相等性

2022-08-30 00:42:00

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

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

如何自定义 Set 对象的相等性以进行深入的对象比较?有没有像Java这样的东西?equals(Object)


答案 1

更新 3/2022

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

由于记录和元组是不可变的(无法修改),并且由于它们很容易按值进行比较(通过其内容,而不仅仅是其对象引用),因此它允许Maps和Set使用对象内容作为键,并且建议的规范为Set和Map显式命名此功能。

这个原始问题要求设置比较的可定制性,以支持深度对象比较。这并没有提出 Set 比较的可定制性,但是如果您使用新的记录或元组而不是对象或数组,它直接支持深度对象比较,因此可以解决此处的原始问题。

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

Mozilla 在这个新提案上的工作可以在这里进行跟踪。


原始答案

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

和 方法只能使它成为相同的实际对象或基元的相同值,并且没有方法插入或替换该逻辑。.has().add().delete()

你可以从 a 和 replace 和 方法中派生出你自己的对象,并首先进行深入的对象比较,以查找该项是否已经在 Set 中,但性能可能并不好,因为底层对象根本没有帮助。您可能只需要对所有现有对象进行暴力迭代,即可在调用原始对象之前使用自己的自定义比较找到匹配项。Set.has().add().delete()Set.add()

以下是本文的一些信息以及对 ES6 功能的讨论:

5.2 为什么我无法配置映射和集如何比较键和值?

问:如果有一种方法可以配置哪些映射键和哪些集合元素被认为是相等的,那就太好了。为什么没有?

答:该功能已被推迟,因为它难以正确有效地实现。一种选择是将回调传递给指定相等性的集合。

Java中可用的另一个选项是通过对象实现的方法指定相等性(Java中的equals()。但是,这种方法对于可变对象来说是有问题的:通常,如果对象发生更改,其在集合中的“位置”也必须更改。但这并不是Java中发生的事情。JavaScript可能会走更安全的路线,即只允许对特殊的不可变对象(所谓的值对象)按值进行比较。按值比较意味着如果两个值的内容相等,则认为两个值相等。基元值在 JavaScript 中按值进行比较。


答案 2

正如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