1

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?

user1506145
  • 5,176
  • 11
  • 46
  • 75
  • If I were you, i'd give an ID to the HTML element and use the concatenation of the ID and `your string` to get a unique key! – sjahan Jun 29 '18 at 14:17
  • @sjahan I'd like to use the feature of ES6 maps to use objects as keys, and I have to be able to do this with any element. – user1506145 Jun 29 '18 at 14:21
  • @user1506145 You can't have an object combined with a string either. And notice that objects as keys are still compared by identity, not by whatever kind of equivalence. – Bergi Jun 29 '18 at 14:23

2 Answers2

2

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>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

Using this custom MultiKeyMap, you can create a map that accepts any number keys based on the number you passed to the constructor when instantiating it. However, note that the tuple of keys is ordered:

class MultiKeyMap extends Map {
  constructor (keys = 1) {
    if (keys < 1) {
      throw new RangeError('keys must be greater than 0')
    }

    // 1 key is just a normal Map
    if (keys === 1) {
      return new Map()
    }

    super()
    this.keys = keys
  }
  
  get (key, ...keys) {
    if (arguments.length !== this.keys) {
      throw new RangeError('Unexpected number of keys')
    }

    // return early
    if (!super.has(key)) {
      return undefined
    }

    return super.get(key).get(...keys)
  }

  // (...keys, value) is an illegal signature
  set (key, ...args) {
    if (args.length !== this.keys) {
      throw new RangeError('Unexpected number of keys')
    }

    if (!super.has(key)) {
      super.set(key, new MultiKeyMap(this.keys - 1))
    }

    return super.get(key).set(...args)
  }
  
  has (key, ...keys) {
    if (arguments.length !== this.keys) {
      throw new RangeError('Unexpected number of keys')
    }

    return super.has(key) && super.get(key).has(...keys)
  }
}

const map = new MultiKeyMap(2)

map.set(document.body, 'my string', 'my value')

console.log(`document.body, 'my string'`)
console.log(map.has(document.body, 'my string'))
console.log(map.get(document.body, 'my string'))
console.log(`'my string', document.body`)
console.log(map.has('my string', document.body))
console.log(map.get('my string', document.body))
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153