2

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>
Terry
  • 63,248
  • 15
  • 96
  • 118
  • 5
    Why did @charlietfl remove his answer, he was essentially correct. basically a DOM node is nothing more than a nested object. When you update an attribute, you are mutating it, but NOT its location on memory. The object needs to be available for garbage collection for the WeakMap to loose its key, removing the DOM from the document tree causes that. For the same reason, WeakMap keys are not enumerable: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap – ibrahim tanyalcin Aug 07 '18 at 22:36
  • 1
    Just an added note to the previous comment: removing an Element from the DOM tree doesn't necessarily mark it as garbage collectable, it will be the case only if the DOM tree was the only place this object was referenced. And in your *remove&recreate* example, both Element objects are two completely different instances. – Kaiido Aug 08 '18 at 01:56

1 Answers1

4

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.

Ben West
  • 4,398
  • 1
  • 16
  • 16