How to customize object equality for JavaScript Set

后端 未结 9 1287
臣服心动
臣服心动 2020-11-22 16:48

New ES 6 (Harmony) introduces new Set object. Identity algorithm used by Set is similar to === operator and so not much suitable for comparing objects:

9条回答
  •  失恋的感觉
    2020-11-22 17:11

    For Typescript users the answers by others (especially czerny) can be generalized to a nice type-safe and reusable base class:

    /**
     * Map that stringifies the key objects in order to leverage
     * the javascript native Map and preserve key uniqueness.
     */
    abstract class StringifyingMap {
        private map = new Map();
        private keyMap = new Map();
    
        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 {
            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 {
            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;
    }
    
    

    Example implementation is then this simple: just override the stringifyKey method. In my case I stringify some uri property.

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

    Example usage is then as if this was a regular Map.

    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
    

提交回复
热议问题