JavaScript WeakMaps / WeakSets: What consequences have "weak" references? - javascript

In trying to understand JavaScript's WeakMaps / WeakSets I have read the MDN-documentation.
There is written: "The WeakSet is weak: References to objects in the collection are held weakly. If there is no other reference to an object stored in the WeakSet, they can be garbage collected.".
Full article: MDN
What does that "they can be garbage collected"?
When I create an object. Then store it an WeakSet. Then set the reference variable null.
Would the object become deleted from the set automatically?

I'm not really sure the above is showing you what a WeakSet is doing..
So I've created a snippet here to show it. Because from the browser you generally don't have access to the GC, I have made it so that it console logs the WeakSet, and waits for you to press a button and console logs again. In the mean time you can then clear the browsers console and force a GC, clearing the console is done as in Chrome console logging the WeakSet will also end up keeping a reference to the objects.
If you do this say in Chrome browser, you should see WeakSet {{..}, {{..}}, showing us that the WeakSet has a reference to 2 objects. After clearing the console, forcing a GC, and clicking the button,. another WeakSet will be console logged, it should show WeakSet {{..}}. basically showing 1 object, proving the GC has done it's job and is keeping a reference to objectA only.
Note: In chrome to force a GC, go to the performance tab, and there is
an icon that looks like a dustbin 🗑️, click this.
ps. If you change the new WeakSet to new Set in the snippet, and do the same thing, you will notice you will still have 2 items in the Set. This is the difference between a Set and a WeakSet.
const ws = new WeakSet();
let objectA = {};
let objectB = {};
ws.add(objectA);
ws.add(objectB);
console.log(ws);
objectB = null;
document.querySelector("button").onclick = function () {
console.log(ws);
}
<p>Look inside Chrome console, you should see a WeakSet, and it should have two values in there. eg. <b>WeakSet {{..}},{..}}</b></p>
<p>Now clear the console, otherwise the console will keep a referece to the weakset entries, and either wait for a while, maybe 30 seconds, or go into chrome's Performace tab and click the collect garbage icon. After doing this, click the button below.</p>
<p>
<button>Click me after console clear and GC</button>
<p>After clicking the above button look in your console again, you should see a Weakset again, but with just one item. eg. <b>WeakSet {{..}}</b></p>

It means the garbage collector will remove the object from memory if it's referenced ONLY by WeakSet or WeakMap.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
var obj = {};
var array = new Array();
while(true) // don't do this
{
array.add(obj);
obj = null;
}
vs
var obj = {};
var ws = new WeakSet();
while(true) // don't do this
{
ws.add(obj);
obj = null;
}
In the second example memory allocated by obj gets cleared, while in the second one it doesn't.

Related

WeakMap doesn't work as expected in Chrome console

According to MDN, a WeakMap object
does not create strong references to its keys, so values in any
WeakMap become candidates for garbage collection as well — as long as
they aren't strongly referred to elsewhere.
But when I try the code below in Chrome, WeakMap doesn't work as expected:
const wm = new WeakMap()
function foo(){
const key = {bar: 1};
const value = {bar: 2};
wm.set(key, value)
}
foo()
console.log(wm)
Why there's still object in the WeakMap ?
The contents of a weakmap will only change when GC (garbage collection) is called.
When testing weakmaps in a webpage, the page has to have a reason to trigger the GC for the weakmap to perform its tasks.
Going on a fairly static site with minimal elements such as github will take a while before the GC is called.
Going to a page like youtube and jumping around the video's timeline will trigger the GC much faster as there will be a lot more memory management happening in the browser.

WeakSet: garbage collection doesn't work? [duplicate]

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?
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.
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)
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.
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").
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.
};
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));
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.
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/

Timing issues considerations when using WeakMap from EcmaScript

What is the proper usage of the WeakMap in JavaScript? What kind of timing issues may occur when I use it? IN particular, I am wondering what would happen in the following situation:
var wm1 = new WeakMap()
var o1 = {},
o2 = function(){},
o3 = window;
// in other method:
wm1.set(o1, 37);
wm1.set(o2, "azerty");
if (wm1.has(o2)) {
//Garbage collection happen here, objects from wm1 may no longer exists
Console.log(wm1.get(o2)) // what will happen here? just undefined? null?
}
how GC will affect WeakMaps?
Update: my bad, I missed the fact that you can't have string as keys in WeakMap, my question does not make if I take into account that fact.
WeakMaps are explicitly designed to not exhibit the least observable garbage collection behaviour. There will be absolutely zero issues.
In your specific situtation, as long as you hold a reference to the object or to the function (through the live variables o1 and o2 that are still on the stack), you will be able to find them in the WeakMap or WeakSet. As soon as you don't hold a reference to them any more, and nobody does, they are eligible for garbage collection (just as usual) - and given that, nobody will be able to try to look them up in the collection.

Possible memory leak in lodash.js during _.bind

I've created a very simple empty web-page with the only link to a lodash library.
Then (directly in Chrome console) I create a simple class:
window.class = function() {
this.bound = _.bind(this.toBind, this);
}
window.class.prototype.toBind = function() {
this.value = 'value';
}
Instances of this class will have only 1 property value and 2 methods: toBind and bound (which is a bound version of toBind).
Then I take a heap snapshot via Chrome dev toolbar - Snapshot1 - this is the initial state of heap.
Then I create an instance of class and delete it:
window.obj = new window.class()
window.obj = null;
delete window.obj;
And finally I take another heap snapshot.
I expect that the obj instance is no longer alive as there are no more references to it (the only one was window.obj but in the last step it was removed). But the comparison of two heap snapshot shows that the object is still alive seems that lodash itself references to it (tested in Chrome 34.0.1847.131).
Can someone explain if this is a real memory leak inside lodash? Or is there any secret meaning of such behavior?
Note: the native bind works correctly.
Thanks a lot!
It was a bug in lodash. Will be fixed in version next to 2.4.1.
Bugtracking ticket. Fixed in commit.

Garbage collection of deleted array element

If I have a large array I'm using all the time and continuously through-out the lifetime of the app, such as a user list. And yes I enjoy using arrays rather than objects for many reasons and can't use splice() because I have index references.
The usual quote for garbage collection:
Since Javascript is garbage collected, you don't need to delete
objects themselves - they will be removed when there is no way to
refer to them anymore.
Does this mean that when I delete an element in an array, since the array can be referred to at any time - the Garbage collector wont free up the deleted element? or does it mean because the object can't be referred to, it will free up memory? - And even if it is, wont my array get filled up with 'undefined' entries which presumably use up at least 1 or 2 bytes of memory? - does anyone know how much memory is used per undefined entry?
Example:
var userList = [];
var userNumber = 0;
userList[userNumber++] = {name:'john',score:200,friends:['abby','micky']};
userList[userNumber++] = {name:'jack',score:200,friends:['betty','billy']};
userList[userNumber++] = {name:'jimm',score:200,friends:['catty','ken']};
delete userList[1];
// will {name:'jack',score:200,friends[ n ]} size of memory be freed
// or a little less to internally reference 'undefined'?
When you delete an array property, its value becomes unreachable*, hence freeable by the garbage collector as soon as you delete them.
a = [0, 1, 2]
alert(1 in a); // true
delete a[1];
alert(1 in a); // false
* - assuming the array element is not otherwise reachable from a property of a reachable object or live local variable.
... - the Garbage collector wont free up the deleted element? or does it mean because the object can't be referred to, it will free up memory?
The garbage collector is never obliged to free any memory, but can reuse any memory previously used by the value of a deleted array property that is not otherwise referenced by any property or live local variable.

Categories

Resources