ES6 Set, WeakSet, Map and WeakMap - javascript

There is already some questions about map and weak maps, like this: What's the difference between ES6 Map and WeakMap? but I would like to ask in which situation should I favor the use of these data structures? Or what should I take in consideration when I favor one over the others?
Examples of the data structures from:https://github.com/lukehoban/es6features
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
Bonus. Which of the above data structures will produce the same/similar result of doing: let hash = object.create(null); hash[index] = something;

This is covered in §23.3 of the specification:
If an object that is being used as the key of a WeakMap key/value pair is only reachable by following a chain of references that start within that WeakMap, then that key/value pair is inaccessible and is automatically removed from the WeakMap.
So the entries in a weak map, if their keys aren't referenced by anything else, will be reclaimed by garbage collection at some point.
In contrast, a Map holds a strong reference to its keys, preventing them from being garbage-collected if the map is the only thing referencing them.
MDN puts it like this:
The key in a WeakMap is held weakly. What this means is that, if there are no other strong references to the key, then the entire entry will be removed from the WeakMap by the garbage collector.
And WeakSet does the same.
...in which situation should I favor the use of this data structures?
Any situation where you don't want the fact you have a map/set using a key to prevent that key from being garbage-collected. Here are some examples:
Having instance-specific information which is truly private to the instance, which looks like this: (Note: This example is from 2015, well before private fields were an option. Here in 2021, I'd use private fields for this.)
let Thing = (() => {
var privateData = new WeakMap();
class Thing {
constructor() {
privateData[this] = {
foo: "some value"
};
}
doSomething() {
console.log(privateData[this].foo);
}
}
return Thing;
})();
There's no way for code outside that scoping function to access the data in privateData. That data is keyed by the instance itself. You wouldn't do that without a WeakMap because it would be a memory leak, your Thing instances would never be cleaned up. But WeakMap only holds weak references, and so if your code using a Thing instance is done with it and releases its reference to the instance, the WeakMap doesn't prevent the instance from being garbage-collected; instead, the entry keyed by the instance is removed from the map.
Holding information for objects you don't control. Suppose you get an object from some API and you need to remember some additional information about that object. You could add properties to the object itself (if it's not sealed), but adding properties to objets outside of your control is just asking for trouble. Instead, you can use a WeakMap keyed by the object to store your extra information.
One use case for WeakSet is tracking or branding: Suppose that before "using" an object, you need to know whether that object has ever been "used" in the past, but without storing that as a flag on the object (perhaps because if it's a flag on the object, other code can see it [though you could use a private field to prevent that]; or because it's not your object [so private fields wouldn't help]). For instance, this might be some kind of single-use access token. A WeakSet is a simple way to do that without forcing the object to stay in memory.
Which of the above data structures will produce the same/similar result of doing: let hash = Object.create(null); hash[index] = something;
That would be nearest to Map, because the string index (the property name) will be held by a strong reference in the object (it and its associated property will not be reclaimed if nothing else references it).

Related

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/

Is WeakMap performance influenced by the Key Object?

When using a WeakMap Object in JavaScript, the get and set methods require a Key to be passed in as argument.
FYI, I'm using WeakMap to simulate private properties for a class, something like this:
window.MyObject = (function(){
let _privateProperty = new WeakMap();
class MyObject {
constructor(value) {
_privateVariable.set(this, value);
}
SamplePrivateGet() {
return _privateVariable.get(this);
}
}
return MyObject;
})();
My question is: is get and set performance influenced by the object used as key?
I know primitive types can't be used as key, but maybe using an object with only one property as key could be faster than using an object that has, say, one thousand properties.
I tried looking at ECMA specifications but it's not specified, my guess is it's because it would depends on the browser's implementation.
EDIT: There are two ways to approach this with WeakMaps, one is declaring a single _privateProperties WeakMap to which I will add my private properties, the other declare one WeakMap for each of the private properties. I'm currently using the second approach so that every WeakMap can be Garbage Collected separately, but maybe going with the first would allow me to make much less .get calls. But I guess that's out of the scope of the question :)
Not sure if this is good idea, but you could make kind of private instance properties by using Symbols.
const PRIVATE_VAR = Symbol('MY_CLASS::PRIVATE_VAR');
const PRIVATE_FUNC = Symbol('MY_CLASS::PRIVATE_FUNC');
export default class MyClass{
constructor(value){
this[PRIVATE_VAR] = value;
}
[PRIVATE_FUNC](){
/* ... */
}
}
Without access to those Symbols values, it is quite hard to access those specific instance properties (plus instance properties defined with Symbols are non-enumerable in classes and objects).
btw 'Private instance methods and accessors' are at stage 3 so such solutions might not be relevant soon
https://github.com/tc39/proposals

Is it ok to create a prototype just to save internal object properties?

When I instantiate an object in my javascript framework, I add a __listeners__ property to it. But then the console.log output shows the__listeners__ property first.
I don't want the __listeners__ property showing up in the beginning of the console.log output, since its an internal property in my framework. I'm looking for a way to somehow push the __listeners__ property to the end of the object.
The only solution I have so far is by adding an extra object in the prototype chain.
create: function() {
var objTemplate=Object.create(this);
var newObj=Object.create(objTemplate);
objTemplate.__listeners__=[];
return newObj;
}
But, the above solution, will cause slower lookups through the prototype chain and creates a slightly unintuitive object hierarchy.
Is this ok or is there a better solution to this problem?
If you can use ES2015's WeakMap, which cannot be shimmed on older environments (but see ¹), creating information related to an object that isn't stored on the object is one of their use cases:
const privateDataCache = new WeakMap();
class MyThingy {
constructor() {
privateDataCache.set(this, {listeners: []});
// ...
}
addListener(listener) {
privateDataCache.get(this).listeners.push(listener);
}
removeListener(listener) {
const data = privateDataCache.get(this);
data.listeners = data.listeners.filter(l => l !== listener);
}
}
Because the keys in a WeakMap are weakly-held, they don't prevent your objects from being garbage collected, and the private data stored in the WeakMap is removed when the key becomes invalid.
That won't work in IE prior to IE11, though. (And IE11's WeakMap is missing some things, but nothing the above relies on.)
¹ Although WeakMap cannot be correctly shimmed/polyfilled, the syntax can be shimmed/polyfilled. core.js does that, implementing the WeakMap by storing data on the key itself. So while not correct, it may well be what you want for your use case.

Retrieved typed objects from IndexedDB

What is the most efficient way to store and retrieve typed Javascript objects in IndexedDB?
The problem is that IndexedDB doesn't store prototype information, so you can only store and retrieve plain objects (or arrays or primitives or a few other types). A workaround I came up with is to explicitly assign __proto__ on objects retrieved from the database. For example, to get a Game object I do
game.__proto__ = Game.prototype;
However, __proto__ assignment has the problem that it is A) technically nonstandard, though supported in practice, and B) deoptimizes the code. In fact, Firefox gives an explicit warning
mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create
Obviously, Object.create isn't a possibility here. Are there any better alternatives to __proto__ assignment?
You could consider storing just the backing-data object itself. Game would become just a proxy for a storable object.
function Game(props) {
this.props = props || {};
}
// An example of property decoration
Game.prototype.set x(value) {
this.props.x = value;
};
Game.prototype.get x() {
return this.props.x;
};
// Use this when initializing a game after retrieving game data from indexedDB store.
// e.g. when creating a new game, use var newGame = Game.fromSerializable(props);
Game.fromSerializable = function(props) {
return new Game(props);
};
// When it comes time to persist the game object, expose the serializable props object
// so that the caller can pass it to store.put/store.add
Game.prototype.toSerializable = function() {
return this.props;
};
This might be simpler than bothering to deal with what can pass through the structured clone algorithm used by indexedDB for read/write, or using some strange one-off hack that other people might struggle to understand.

JavaScript linking two objects

What is a memory efficient way to link two objects? If you store objects using two arrays with corresponding index values the object won't be released from memory.
The implementation should look like the following.
var obj ={};
var linkedobj = getLinkedObject(obj);
var obj2 ={}
var linkedobj2 = getLinkedObject(obj2);
Objects are general containers
If all you want to do is associate one object with another one, then why not have them point to one another?
var obj = {};
var obj2 = {};
obj.linkedObject = obj2;
obj2.linkedObject = obj;
This would a normal thing to do, and doesn't have bad implications on memory.
Releasing memory
If you are asking about holding a table full of object references, you are correct that the object will not disappear until all of the live references to it are gone. Javascript has a garbage collector, and as long as your table or array has a good reference, it will be backed by memory for you. If you give an object to a table and then want it to be deleted from memory entirely, you can simply remove it from your table also. You could also just remove the table, assuming it doesn't need to hold anything else.
If you are determined to have a getLinkedObject function return an object for another object, you can still allow the objects to each hold the associate reference.
function getLinkedObject(anObject) {
return anObject.linkedObject;
}
This would probably be accompanied by a counterpart:
function linkObjects(anObject, anotherObject) {
anObject.linkedObject = anotherObject;
anotherObject.linkedObject = anObject;
}
Doing this allows you to worry a little less about memory management, which in Javascript (a high-level, dynamic environment with a garbage collector) is typically appropriate.
Unless you use keparo's answer, which from your follow-up comment sounds like not an option, what you're asking for is basically impossible. Because, since you can't augment the original object, you need some method of storing a reference to it. But (since JavaScript has no concept of weak references) this will always prevent it from being garbage collected, contradicting your requirements statement.

Categories

Resources