78

The WeakSet is supposed to store elements by weak reference. That is, if an object is not referenced by anything else, it should be cleaned from the WeakSet.

I have written the following test:

var weakset = new WeakSet(),
    numbers = [1, 2, 3];

weakset.add(numbers);
weakset.add({name: "Charlie"});

console.log(weakset);

numbers = undefined;

console.log(weakset);

Even though my [1, 2, 3] array is not referenced by anything, it's not being removed from the WeakSet. The console prints:

WeakSet {[1, 2, 3], Object {name: "Charlie"}}
WeakSet {[1, 2, 3], Object {name: "Charlie"}}

Why is that?

Plus, I have one more question. What is the point of adding objects to WeakSets directly, like this:

weakset.add({name: "Charlie"});

Are those Traceur's glitches or am I missing something?

And finally, what is the practical use of WeakSet if we cannot even iterate through it nor get the current size?

Robo Robok
  • 21,132
  • 17
  • 68
  • 126
  • [The WeakSet object lets you store weakly held objects in a collection.](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) – Rahul Tripathi May 31 '15 at 09:55
  • 13
    @RahulTripathi yes I have seen this doc, but it doesn't answer my questions. – Robo Robok May 31 '15 at 10:00
  • 1
    @Teemu yes it doesn't remove the variable, but it removes the reference. And in this case, it was the last reference, which means that the array object becomes innaccessible garbage. :) – Robo Robok May 31 '15 at 10:16
  • 5
    Related: [What are the actual uses of ES6 WeakMap?](http://stackoverflow.com/q/29413222/218196) – Felix Kling May 31 '15 at 22:06
  • 4
    One thing not mentioned in any of the solutions is about your question about the console. I just dealt with this and discovered that the console keeps a reference to a WeakSet() so it will remain there until you close and re-open the console. Spent forever in WTF mode until I did that and boom - all the WeakSets look like they should! I feel like that property could provide some really nice debugging into the garbage collector and confirming things are being removed as expected. – Braden Rockwell Napier May 30 '17 at 06:32

8 Answers8

79

it's not being removed from the WeakSet. Why is that?

Most likely because the garbage collector has not yet run. However, you say you are using Traceur, so it just might be that they're not properly supported. I wonder how the console can show the contents of a WeakSet anyway.

What is the point of adding objects to WeakSets directly?

There is absolutely no point of adding object literals to WeakSets.

What is the practical use of WeakSet if we cannot even iterate through it nor get the current size?

All you can get is one bit of information: Is the object (or generically, value) contained in the set?

This can be useful in situations where you want to "tag" objects without actually mutating them (setting a property on them). Lots of algorithms contain some sort of "if x was already seen" condition (a JSON.stringify cycle detection might be a good example), and when you work with user-provided values the use of a Set/WeakSet would be advisable. The advantage of a WeakSet here is that its contents can be garbage-collected while your algorithm is still running, so it helps to reduce memory consumption (or even prevents leaks) when you are dealing with lots of data that is lazily (possibly even asynchronously) produced.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 19
    Thank you for using the word "tagging" - I understand the entire point now. So is iteration and `.size` unavailable in WeakSets, because they wouldn't be reliable? Garbage collector works whenever it wants to, so that information wouldn't be of any use. But `.has()` is different story, because if we have any reference to pass it to `.has()` method, the garbage collector for sure didn't clean it. Is my understanding reasonable? :) – Robo Robok May 31 '15 at 10:24
  • 3
    @RoboRobok: Exactly that :-) One could make `WeakSet` implementations that are iterable, but those can lead to non-deterministic algorithms (depending on GC behaviour) if used in the wrong way, and therefore the ES committee decided not to make the contents available. – Bergi May 31 '15 at 10:28
  • 1
    @RoboRobok I think the other answer shows very well the main use of the WeakSet. I don't think it will be used often in application code, but very often in libraries like jQuery and Angular. There are probably inefficiencies on how such things are handled in a SPA (Single Page Application) with heavy AJAX data loading, where memory management gets tricky over long periods of time. When you have a standard page-oriented website memory is thrown away anyways, so memory management is less of an issue. – pid May 31 '15 at 10:29
  • @pid Thank you for your explanation. One more thing: if adding anything to the WeakSet directly doesn't make sense, could it (in theory) throw an error if we try to do it? Or maybe it's impossible in JavaScript to determine whether something is passed by variable or not? – Robo Robok May 31 '15 at 11:18
  • 1
    @RoboRobok: That's more something for a linter, or an implementation-dependent feature (relying on internal representation stuff). In plain JavaScript, all objects are reference values and when an object is passed to a function, there is no observable difference between an object that is constructed on the fly from a literal and an object that is stored in a variable. – Bergi May 31 '15 at 12:55
  • @Bergi that's what I thought. I wouldn't mind if they decide to make it throw an error internally. – Robo Robok May 31 '15 at 16:01
  • @RoboRobok: I don't think they will - the gain is not worth the implementation costs. And no sane person would write such code anyway. Also, should it then detect stuff like `let x = {…}; ws.add(x); // x no more used` as well? No, it must not, because at some point the mistake becomes normal usage of the WeakSet. – Bergi May 31 '15 at 17:20
  • "I wonder how the console can show the contents of a WeakSet anyway" - why wouldn't it be able to - just because something isn't accessible from JavaScript doesn't mean the console can't access it - the same way it can access closure scope, or the last expression. That said - since it's a shim a better guess would be that it's not really properly implemented. – Benjamin Gruenbaum Jun 02 '15 at 14:56
  • Also - I'm not convinced by the "JSON.stringify cycle detection", WeakSet is about _lifetime_, in a `JSON.stringify` you'd just discard the regular `Set` when you're done it doesn't need to outlive the items. So I really don't see any practical use cases in this answer (I understand the "tag something" sentiment though - just not where to apply it). – Benjamin Gruenbaum Jun 02 '15 at 14:58
  • @BenjaminGruenbaum: Sure a native `console.log` might be able to inspect a native `WeakSet`, but I wouldn't even expect that as then the console maintains a (non-weak?) reference to the contents. And I was even more suspicious about a shim. – Bergi Jun 02 '15 at 15:49
  • @BenjaminGruenbaum: Good point. I've adapted my answer to focus on the lifetime aspect, where you'd want to discard values before you can discard the `WeakSet`. – Bergi Jun 02 '15 at 15:51
  • Note that “the object (or generically, value)” is mis-leading — WeakSets are defined to [*only* be able to hold Objects](http://www.ecma-international.org/ecma-262/6.0/#sec-weakset.prototype.add). – ELLIOTTCABLE Jan 27 '17 at 09:18
  • @Bergi, Re "one bit of information", that's wrong. As long as you don't manually `weakset.delete()`, then `.has(obj)` **will always return true**. There seems to be no observable way to see if the GC has already taken the object, for to do `.has(obj)`, you would first need a ref to `obj` which would prevent it from being GCed. – Pacerier Sep 18 '17 at 10:43
  • @Pacerier The bit refers to whether the object is contained in the WeakSet, not whether it has been garbage collected. `weakset.has(obj)` will return `false` if `obj` hasn't been added to `weakset` before. – Bergi Sep 18 '17 at 12:57
  • @Bergi, **You might as well use a set**, not a weakset. To ensure "*...garbage-collected while your algorithm is still running..*", just null the variable or the entry in the set: Weakset not needed. – Pacerier Sep 18 '17 at 16:26
  • @Pacerier The point is that you would not want to delete it before the algorithm has ended, but that it's possible the value is no longer needed - the garbage collector can know that, your algorithm (that would manually `delete` it) does not. – Bergi Sep 18 '17 at 16:29
39

This is a really hard question. To be completely honest I had no idea in the context of JavaScript so I asked in esdiscuss and got a convincing answer from Domenic.

WeakSets are useful for security and validation reasons. If you want to be able to isolate a piece of JavaScript. They allow you to tag an object to indicate it belongs to a special set of object.

Let's say I have a class ApiRequest:

class ApiRequest {
  constructor() {
    // bring object to a consistent state, use platform code you have no direct access to
  }

  makeRequest() {
    // do work 
  }
}

Now, I'm writing a JavaScript platform - my platform allows you to run JavaScript to make calls - to make those calls you need a ApiRequest - I only want you to make ApiRequests with the objects I give you so you can't bypass any constraints I have in place.

However, at the moment nothing is stopping you from doing:

ApiRequest.prototype.makeRequest.call(null, args); // make request as function
Object.create(ApiRequest.prototype).makeRequest(); // no initialization
function Foo(){}; Foo.prototype = ApiRequest.prototype; new Foo().makeRequest(); // no super

And so on, note that you can't keep a normal list or array of ApiRequest objects since that would prevent them from being garbage collected. Other than a closure, anything can be achieved with public methods like Object.getOwnPropertyNames or Object.getOwnSymbols. So you one up me and do:

const requests = new WeakSet();
class ApiRequest {
  constructor() {
    requests.add(this);
  }

  makeRequest() {
    if(!request.has(this)) throw new Error("Invalid access");
    // do work
  }
}

Now, no matter what I do - I must hold a valid ApiRequest object to call the makeRequest method on it. This is impossible without a WeakMap/WeakSet.

So in short - WeakMaps are useful for writing platforms in JavaScript. Normally this sort of validation is done on the C++ side but adding these features will enable moving and making things in JavaScript.

(Of course, everything a WeakSet does a WeakMap that maps values to true can also do, but that's true for any map/set construct)

(Like Bergi's answer suggests, there is never a reason to add an object literal directly to a WeakMap or a WeakSet)

Nick
  • 138,499
  • 22
  • 57
  • 95
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    Can't you do most of this with strong Sets as well? You would have to remove them at some point or leak requests, but that could easily be done in the `makeRequest` method as requests are typically considered one-time-use. – ssube Jun 02 '15 at 16:16
  • 2
    @ssube what if multiple `makeRequest` calls can be made? What if `ApiRequest` instances get passed but sometimes not used at all (0 calls)? The reason it has to be weak is to not bind the lifetime of the requests to the set. – Benjamin Gruenbaum Jun 02 '15 at 16:17
  • @BenjaminGruenbaum, **1)** If the usefulness is limited to ease of writing such platforms, truth be told it should never have been added to the main language at all. **2)** Such platforms can be written using `if(static_symbol !== arg1) throw new Error("Invalid access");`. https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap#comment79534889_29416340 Using weakset for this is pretty much a hack. – Pacerier Sep 18 '17 at 21:28
  • @Pacerier 2) no it can't, and it's certainly not a hack - in case you don't know the people whose reply I got in esdiscuss are the people adding stuff to the language :D – Benjamin Gruenbaum Apr 17 '18 at 18:16
14

By definition, WeakSet has only three key functionalities

  • Weakly link an object into the set
  • Remove a link to an object from the set
  • Check if an object has already been linked to the set

Sounds more pretty familiar?

In some application, developers may need to implement a quick way to iterate through a series of data which is polluted by lots and lots of redundancy but you want to pick only ones which have not been processed before (unique). WeakSet could help you. See an example below:

var processedBag = new WeakSet();
var nextObject = getNext();
while (nextObject !== null){
    // Check if already processed this similar object?
    if (!processedBag.has(nextObject)){
        // If not, process it and memorize 
        process(nextObject);
        processedBag.add(nextObject);
    }
    nextObject = getNext();
}

One of the best data structure for application above is Bloom filter which is very good for a massive data size. However, you can apply the use of WeakSet for this purpose as well.

TaoPR
  • 5,932
  • 3
  • 25
  • 35
  • 2
    Note that `processedBag.has(nextObject)` will always return true after you have inserted the object, because by having a reference to it, you are stopping the GC from collecting it. **In other words, your might as well have used a normal set** if you do not need the autodelete capabilities of a weakset when the var goes out of scope. – Pacerier Sep 18 '17 at 16:09
  • 1
    Won't the line `nextObject = getNext();` give another object which may not be in the `processedBag`? – Paul Stelian Jul 07 '18 at 01:02
5

A "weak" set or map is useful when you need to keep an arbitrary collection of things but you don't want their presence in the collection from preventing those things from being garbage-collected if memory gets tight. (If garbage collection does occur, the "reaped" objects will silently disappear from the collection, so you can actually tell if they're gone.)

They are excellent, for example, for use as a look-aside cache: "have I already retrieved this record, recently?" Each time you retrieve something, put it into the map, knowing that the JavaScript garbage collector will be the one responsible for "trimming the list" for you, and that it will automatically do so in response to prevailing memory conditions (which you can't reasonably anticipate).

The only drawback is that these types are not "enumerable." You can't iterate over a list of entries – probably because this would likely "touch" those entries and so defeat the purpose. But, that's a small price to pay (and you could, if need be, "code around it").

Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
1

WeakSet is a simplification of WeakMap for where your value is always going to be boolean true. It allows you to tag JavaScript objects so to only do something with them once or to maintain their state in respect to a certain process. In theory as it doesn't need to hold a value it should use a little less memory and perform slightly faster than WeakMap.

var [touch, untouch] = (() => {
    var seen = new WeakSet();
    return [
        value => seen.has(value)) || (seen.add(value), !1),
        value => !seen.has(value) || (seen.delete(value), !1)
    ];
})();

function convert(object) {
    if(touch(object)) return;
    extend(object, yunoprototype); // Made up.
};

function unconvert(object) {
    if(untouch(object)) return;
    del_props(object, Object.keys(yunoprototype)); // Never do this IRL.
};
jgmjgm
  • 4,240
  • 1
  • 25
  • 18
  • 1
    Your example code doesn't explain how weakset is used. – Pacerier Sep 18 '17 at 16:15
  • I must have been in a rush. The description is more important. The code is stubble/prototype for altering the object once and only once (such as extending it, runtime polymorphism). – jgmjgm Sep 18 '17 at 17:36
1

Your console was probably incorrectly showing the contents due to the fact that the garbage collection did not take place yet. Therefore since the object wasn't garbage collected it would show the object still in weakset.

If you really want to see if a weakset still has a reference to a certain object then use the WeakSet.prototype.has() method. This method, as the name implies returns a boolean indicating wether the object still exists in the weakset.

Example:

var weakset = new WeakSet(),
    numbers = [1, 2, 3];

weakset.add(numbers);
weakset.add({name: "Charlie"});

console.log(weakset.has(numbers));

numbers = undefined;

console.log(weakset.has(numbers));
Willem van der Veen
  • 33,665
  • 16
  • 190
  • 155
0

Let me answer the first part, and try to avoid confusing you further.

The garbage collection of dereferenced objects is not observable! It would be a paradox, because you need an object reference to check if it exists in a map. But don't trust me on this, trust Kyle Simpson: https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/es6%20%26%20beyond/ch5.md#weakmaps

The problem with a lot of explanations I see here, is that they re-reference a variable to another object, or assign it a primitive value, and then check if the WeakMap contains that object or value as a key. Of course it doesn't! It never had that object/value as a key!

So the final piece to this puzzle: why does inspecting the WeakMap in a console still show all those objects there, even after you've removed all of your references to those objects? Because the console itself keeps persistent references to those Objects, for the purpose of being able to list all the keys in the WeakMap, because that is something that the WeakMap itself cannot do.

Spike Sagal
  • 191
  • 1
  • 4
0

While I'm searching about use cases of Weakset I found these points:

"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."

##################################

They are black boxes: we only get any data out of a WeakSet if we have both the WeakSet and a value.

##################################

Use Cases:

1 - to avoid bugs

2 - it can be very useful in general to avoid any object to be visited/setup twice

Refrence: https://esdiscuss.org/topic/actual-weakset-use-cases

3 - The contents of a WeakSet can be garbage collected.

4 - Possibility of lowering memory utilization.

Refrence: https://www.geeksforgeeks.org/what-is-the-use-of-a-weakset-object-in-javascript/

##################################

Example on Weakset: https://exploringjs.com/impatient-js/ch_weaksets.html

I Advice you to learn more about weak concept in JS: https://blog.logrocket.com/weakmap-weakset-understanding-javascript-weak-references/

HamzaElkotb
  • 138
  • 1
  • 7