Skip to content
Advertisement

Why use of a WeakSet in detecting circular references make sense?

I’m trying to understand the example given by WeakSet documentation here.

// Execute a callback on everything stored inside an object
function execRecursively(fn, subject, _refs = null){
  if(!_refs)
    _refs = new WeakSet();

  // Avoid infinite recursion
  if(_refs.has(subject))
    return;

  fn(subject);
  if("object" === typeof subject){
    _refs.add(subject);
    for(let key in subject)
      execRecursively(fn, subject[key], _refs);
  }
}

const foo = {
  foo: "Foo",
  bar: {
    bar: "Bar"
  }
};

foo.bar.baz = foo; // Circular reference!
execRecursively(obj => console.log(obj), foo);

In the doc it says:

The WeakSet is weak, meaning references to objects in a WeakSet are held weakly. If no other references to an object stored in the WeakSet exist, those objects can be garbage collected.

Object foo is defined outside of execRecursively function. The WeakSet is defined inside of it. So, there is a reference to the object held in the Weakset which is outside of the scope of the function.

The doc continues with:

The number of objects or their traversal order is immaterial, so a WeakSet is more suitable (and performant) than a Set for tracking object references, especially if a very large number of objects is involved.

Now, my question is how this code can be more performant than the time when a Set is used? Because, even in current example there is a reference to the foo which prevents the garbage collector to remove the object.

Advertisement

Answer

How can this code be more performant than the time when a Set is used?

Like the docs say, a WeakSet doesn’t keep track of the number of objects or the order in which they were put in the collection, so there’s a tiny bit less overhead.

In the current example there is a reference to the foo which prevents the garbage collector to remove the object.

Yes, however that is specific to your example. The weakness only gets interesting (and useful) when the objects are lazily generated while traversing the structure. See the following example:

function generate(n) {
    if (n <= 0) return foo;
    else return {
        value: "x".repeat(n),
        get next() { return generate(n-1); },
    }
}
const foo = generate(100000);
let sum = 0;
execRecursively(obj => { sum += obj.value.length, foo);
console.log(sum);

If execRecursively would use a Set, during the execution it would need to keep a hundred thousand objects that contain very long strings in memory. When using a WeakSet, the objects can already be garbage-collected during the execution.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement