Actual WeakSet Use Cases
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!");
}
}
}
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);
}
}
}
}
Caitlin, in that example a normal Set works just as well. It can get gc'ed when you leave the iterate function.
Yes, we had that discussion on #whatwg already, I haven’t had my coffee yet =)
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.
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.
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!");
}
}
}
(I edited the broken format of my previous post)
What other use cases are there?
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.
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.
You can't do "branding" by properties on frozen objects.
Would anyone mind sharing some examples of the use cases Andrea and Andy mentioned?
wickedElements WebReflection/wicked-elements#wickedelements is one
example:
- you register CSS selectors used through MutationObserver, so that connected nodes are passed to the 'connected' listener
- you setup nodes in such listener only if these were not already setup
- 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:
- with modules scoped utilities, you don't leak undesired details
- you don't need to cleanup after or change state unless you provide a way to stop observing
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
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
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