Actual WeakSet Use Cases

# Benjamin Gruenaum (4 years ago)

So umm... not to be annoying but I've been digging through esdiscuss and various blog posts online. I couldn't really find any use case for WeakSet (plenty of threads about naming things :P).

Most material about it online fails to distinguish it from what one would use a regular Set for. All the use cases I know for WeakSet for tagging objects aren't really relevant in JS (for example - in shared memory threading scenarios).

Can someone show me a convincing actual use case for WeakSet that can't better be solved without it?

Thanks, and sorry, Benjamin

# Domenic Denicola (4 years ago)

WeakSets are perfect for branding and are how I would expect web platform class branding to be explained.

const foos = new WeakSet();

class Foo {
  constructor() {
    foos.add(this);
  }
  
  method() {
    if (!foos.has(this)) {
      throw new TypeError("Foo.prototype.method called on an incompatible object!");
    }
  }
}
# Caitlin Potter (4 years ago)

You could use it to avoid bugs involving circular references when iterating, for example:

function iterate(O, fn) {
  if (O == null) return O;
  let objects = new WeakSet();
  iterate_inner(‘’, O, objects, fn);
  return O;

  function iterate_inner(name, O, objects, fn) {
    for (let key of Reflect.ownKeys(O)) {
      let value = O[key];
      let niceName = name ? `${name}.${key}` : key;

      if (typeof value === “object” && value) {
        // Avoid recursing into circular reference
        if (objects.has(value)) {
          continue;
        } else {
           fn(value, niceName);
           objects.add(value);
           iterate_inner(niceName, value, objects, fn);
        }
      } else {
        fn(value, niceName);
      }
    }
  }
}
# Erik Arvidsson (4 years ago)

Caitlin, in that example a normal Set works just as well. It can get gc'ed when you leave the iterate function.

# Caitlin Potter (4 years ago)

Yes, we had that discussion on #whatwg already, I haven’t had my coffee yet =)

# Benjamin Gruenaum (4 years ago)

Thanks Domenic,

Elaborating on your example with more details. Let's say you need to make sure at a certain point that an object has not been tinkered with by user code (for security reasons). You can't check the prototype or a symbol since those can be faked and you can't keep a regular Set because that would prevent any Foo object from ever being garbage collected.

# Tab Atkins Jr. (4 years ago)

Exactly. WeakSet's use-cases are all in the same family as WeakMap, just with a simpler set-up - rather than associating arbitrary data with the object, you just associate "is in this set" with it.

# #!/JoePea (5 months ago)

WeakSets are perfect for branding and are how I would expect web platform class branding to be explained.

const foos = new WeakSet();

class Foo {
  constructor() {
    foos.add(this);
  }

  method() {
    if (!foos.has(this)) {
      throw new TypeError("Foo.prototype.method called on an incompatible object!");
    }
  }
}

Just curious, is that effectively the same as what the (current) private fields proposal offers?

class Foo {
  #isFoo = true
  
  method() {
    if (this.#isFoo) {
      throw new TypeError("Foo.prototype.method called on an incompatible object!");
    }
  }
}
# #!/JoePea (5 months ago)

(I edited the broken format of my previous post)

What other use cases are there?

# Andrea Giammarchi (5 months ago)

WeakSet can be very useful in general to avoid any object to be visited/setup twice, not just those coming from user-land classes.

Circular references, mixins, DOM nodes one-off events handling, and so on and so fort.

# Andy Earnshaw (5 months ago)

This is pretty much what I used it for in a previous job role. We loaded and unloaded various iframes, registering APIs and custom elements inside them, adding the window object to a WeakSet so the initialisation only ran once.

# Michał Wadas (5 months ago)

You can't do "branding" by properties on frozen objects.

# Scott Rudiger (5 months ago)

Would anyone mind sharing some examples of the use cases Andrea and Andy mentioned?

# Andrea Giammarchi (5 months ago)

wickedElements WebReflection/wicked-elements#wickedelements is one

example:

  1. you register CSS selectors used through MutationObserver, so that connected nodes are passed to the 'connected' listener
  2. you setup nodes in such listener only if these were not already setup
  3. any other disconnect/connect event won't bother nodes setup, if these already handled

The disconnected WebReflection/disconnected#disconnected utility (used

in hyperHTML and lighterhtml): if the node part of the group that has been connected or disconnected was observed, meaning that the WeakSet knew it already, then the event triggers, otherwise nothing happens.

In both cases the pattern is pretty similar:

const observed = new WeakSet; const observe = node => { observed.add(node); }; const magic = event => { console.log('magic', event.currentTarget); }; const addMagicIfObserved = node => { if (observed.has(node)) node.addListener('magic', magic); for (const child of node.children) addMagicIfObserved(child); }; new MutationObserver(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { addMagicIfObserved(node); } } }).observe(document, {childList: true, subtree: true});

The list of pros is pretty simple too:

  1. with modules scoped utilities, you don't leak undesired details
  2. you don't need to cleanup after or change state unless you provide a way to stop observing
# Isiah Meadows (5 months ago)

You can with private properties. Verified this in Chrome's console. (Chrome is currently shipping private properties by default.)

class A { constructor(arg) { return arg } }
class B extends A {
    #tag
    static isB(value) {
        try {
            value.#tag
            return true
        } catch {
            return false
        }
    }
}

var object = Object.freeze({foo: true})
new B(object)
console.log(B.isB(object)) // logs `true`

This may seem very odd, but it's consistent with the concept of private fields being sugar for weak maps.


Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# Benjamin Gruenbaum (5 months ago)

Funny to see the issue is still discussed, I've actually used WeakSets a bunch in the last few years.

Here's code from last weak that detects with a mutation observer when a new iframe is attached to the DOM (simplified): gist.github.com/benjamingr/9afb875d1d87377e7e66b166cf1905b5