Search code examples
javascriptes6-map

Composing keys to ES6 Map made of both primitives and objects


ES6 Maps makes it possible to use keys with any value, including functions, objects, and any primitive. I would like to create a combined key with a string and a reference to a DOM-node.

var map = new Map();
var myKey = document.body + 'my string'

map.set(myKey, 'my value')

Obviously this will not work, the addition above will evaluate to [object HTMLBodyElement]my string. How would I go about doing this?


Solution

  • You can't do what you literally described, but you can have a map of maps. I'd probably use a WeakMap keyed by the DOM reference (so that it doesn't force the element to remain in memory if it's removed by DOM manipulation), where the value is a Map keyed by the relevant string. E.g.:

    let entriesByElement = new WeakMap();
    

    Setting an element:

    let map = entriesByElement.get(document.body);
    if (!map) {
        map = new Map();
        entriesByElement.set(document.body, map);
    }
    map.set(keyString, value);
    

    Getting an element:

    let map = entriesByElement.get(document.body);
    let value = map && map.get(keyString);
    

    (That example assumes you won't have undefined as a valid stored value.)

    You could wrap that up in a class.

    Example:

    class ExampleStore {
        constructor() {
            this.entriesByElement = new WeakMap();
        }
        set(element, string, value) {
            let map = this.entriesByElement.get(element);
            if (!map) {
                map = new Map();
                this.entriesByElement.set(element, map);
            }
            map.set(string, value);
        }
        get(element, string) {
            const map = this.entriesByElement.get(element);
            return map && map.get(string);
        }
    }
    
    const store = new ExampleStore();
    
    let div1 = document.getElementById("one");
    let div2 = document.getElementById("two");
    
    store.set(div1, "a", "ayy");
    store.set(div1, "b", "bee");
    
    store.set(div2, "a", "alpha");
    
    console.log(store.get(div1, "a")); // "ayy"
    console.log(store.get(div1, "b")); // "bee"
    
    console.log(store.get(div2, "a")); // "alpha"
    console.log(store.get(div2, "b")); // undefined (doesn't have that entry)
    
    // Removing div1
    document.body.removeChild(div1);
    div1 = null;
    
    console.log(store.get(div1, "a")); // May be undefined, if the element has been
                                       // cleaned up (it is for me on Chrome,
                                       // Firefox and Edge), since we're using a
                                       // WeakMap.
    <div id="one"></div>
    <div id="two"></div>