新的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)的东西?


当前回答

对于Typescript用户,其他人(尤其是czerny)的答案可以概括为一个良好的类型安全且可重用的基类:

/**
 * Map that stringifies the key objects in order to leverage
 * the javascript native Map and preserve key uniqueness.
 */
abstract class StringifyingMap<K, V> {
    private map = new Map<string, V>();
    private keyMap = new Map<string, K>();

    has(key: K): boolean {
        let keyString = this.stringifyKey(key);
        return this.map.has(keyString);
    }
    get(key: K): V {
        let keyString = this.stringifyKey(key);
        return this.map.get(keyString);
    }
    set(key: K, value: V): StringifyingMap<K, V> {
        let keyString = this.stringifyKey(key);
        this.map.set(keyString, value);
        this.keyMap.set(keyString, key);
        return this;
    }

    /**
     * Puts new key/value if key is absent.
     * @param key key
     * @param defaultValue default value factory
     */
    putIfAbsent(key: K, defaultValue: () => V): boolean {
        if (!this.has(key)) {
            let value = defaultValue();
            this.set(key, value);
            return true;
        }
        return false;
    }

    keys(): IterableIterator<K> {
        return this.keyMap.values();
    }

    keyList(): K[] {
        return [...this.keys()];
    }

    delete(key: K): boolean {
        let keyString = this.stringifyKey(key);
        let flag = this.map.delete(keyString);
        this.keyMap.delete(keyString);
        return flag;
    }

    clear(): void {
        this.map.clear();
        this.keyMap.clear();
    }

    size(): number {
        return this.map.size;
    }

    /**
     * Turns the `key` object to a primitive `string` for the underlying `Map`
     * @param key key to be stringified
     */
    protected abstract stringifyKey(key: K): string;
}

示例实现很简单:重写stringifyKey方法。在我的情况下,我stringify一些uri属性。

class MyMap extends StringifyingMap<MyKey, MyValue> {
    protected stringifyKey(key: MyKey): string {
        return key.uri.toString();
    }
}

示例用法是,如果这是一个常规Map<K, V>。

const key1 = new MyKey(1);
const value1 = new MyValue(1);
const value2 = new MyValue(2);

const myMap = new MyMap();
myMap.set(key1, value1);
myMap.set(key1, value2); // native Map would put another key/value pair

myMap.size(); // returns 1, not 2

其他回答

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

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

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

对那些在谷歌上发现这个问题的人(像我一样)想要使用对象作为键来获得一个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");

输出:

工作

正如其他人所说,当前版本的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} ] * / //注意:这将选择列表中最后一个重复的项。

正如在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方法返回相同的值时,才认为它们相等。

正如上面的答案所提到的,自定义相等性对于可变对象是有问题的。好消息是(我很惊讶还没有人提到这一点)有一个非常流行的库叫做immutable-js,它提供了一组丰富的不可变类型,这些类型提供了您正在寻找的深层值相等语义。

下面是你使用immutable-js的例子:

const { Map, Set } = require('immutable');
var set = new Set();
set = set.add(Map({a:1}));
set = set.add(Map({a:1}));
console.log([...set.values()]); // [Map {"a" => 1}]