javascriptweakmap

Using DOM node as key in WeakMap


When working with WeakMap I came across a scenario where I find quite puzzling: let's say I have a DOM node with some data I want to store, and I store it in a WeakMap using the element/node itself as the key, and the arbitrary data as value.

Between storing and retrieving the entry from WeakMap, the DOM node is changed: let's say, it's id attribute was updated. I would expect that the .get(<Node>) will return undefined since the node has been mutated, but it still somehow returns it.

However, when I destroy the node in the DOM tree and re-render it—even without changing any of its attributes or properties—it is now considered a new element in WeakMap when storing it.

My question is: why does changing a DOM node, which was used as a key to store arbitrary data, in WeakMap, not return undefined? Here is a proof-of-concept example, with instructions to reproduce the behaviour:

  1. Click on "Store element"
  2. Click on "Retrieve element" to verify that element is indeed stored in WeakMap
  3. Click on "Mutate element". The element should have its id attribute updated.
  4. Click on "Retrieve element": even when the element has been mutated, it can still retrieve the value set with the original element.
  5. Click on "Destroy and recreate element". The Node is removed from the DOM, and its outerHTML is used to create an identical looking element.
  6. Click on "Retrieve element": WeakMap correctly reports that nothing is found, since we are using an entirely new DOM node as a key.

const map = new WeakMap();

// Store element in WeakMap
document.getElementById('set').addEventListener('click', () => {
  const el = document.querySelector('#content > div');
  map.set(el, el.outerHTML);
  console.log('Element stored in WeakMap');
});

// Retrieve element from WeakMap
document.getElementById('get').addEventListener('click', () => {
  const el = document.querySelector('#content > div');
  const elHTML = map.get(el);
  if (elHTML)
    console.log(`Element found in WeakMap, it's data: ${elHTML}`);
  else
    console.log('Element not found in Weakmap!');
});

// Mutate the DOM node, let's say by giving it a new unique ID
let n = 0;
document.getElementById('mutate').addEventListener('click', () => {
  document.querySelector('#content > div').id = `test${n}`;
  console.log(`Element ID updated to: "test${n}"`);
  n++;
});

// Destroy and recreate element
document.getElementById('destroy_and_recreate').addEventListener('click', () => {
  const target = document.querySelector('#content > div');
  const targetHTML = target.outerHTML;
  target.remove();
  document.getElementById('content').innerHTML = targetHTML;
  console.log('Element destroyed and recreated');
});
<section id="content">
  <div id="test">Lorem ipsum dolor sit amet</div>
</section>
<hr />
<button type="button" id="set">Store element</button>
<button type="button" id="get">Retrieve element</button>
<hr />
<button type="button" id="mutate">Mutate element</button>
<button type="button" id="destroy_and_recreate">Destroy and recreate element</button>


Solution

  • The same reason that {} !== {}; it’s not important that two objects have the same properties, or if they were changed, only whether they actually are the same object - technically, the same location in memory. If you want to compare objects by properties and values, use a deep equal function like the one in Lodash.