What are the actual uses of ES6 WeakMap? - javascript

What are the actual uses of the WeakMap data structure introduced in ECMAScript 6?
Since a key of a weak map creates a strong reference to its corresponding value, ensuring that a value which has been inserted into a weak map will never disappear as long as its key is still alive, it can't be used for memo tables, caches or anything else that you would normally use weak references, maps with weak values, etc. for.
It seems to me that this:
weakmap.set(key, value);
...is just a roundabout way of saying this:
key.value = value;
What concrete use cases am I missing?

Fundamentally
WeakMaps provide a way to extend objects from the outside without interfering with garbage collection. Whenever you want to extend an object but can't because it is sealed - or from an external source - a WeakMap can be applied.
A WeakMap is a map (dictionary) where the keys are weak - that is, if all references to the key are lost and there are no more references to the value - the value can be garbage collected. Let's show this first through examples, then explain it a bit and finally finish with real use.
Let's say I'm using an API that gives me a certain object:
var obj = getObjectFromLibrary();
Now, I have a method that uses the object:
function useObj(obj){
doSomethingWith(obj);
}
I want to keep track of how many times the method was called with a certain object and report if it happens more than N times. Naively one would think to use a Map:
var map = new Map(); // maps can have object keys
function useObj(obj){
doSomethingWith(obj);
var called = map.get(obj) || 0;
called++; // called one more time
if(called > 10) report(); // Report called more than 10 times
map.set(obj, called);
}
This works, but it has a memory leak - we now keep track of every single library object passed to the function which keeps the library objects from ever being garbage collected. Instead - we can use a WeakMap:
var map = new WeakMap(); // create a weak map
function useObj(obj){
doSomethingWith(obj);
var called = map.get(obj) || 0;
called++; // called one more time
if(called > 10) report(); // Report called more than 10 times
map.set(obj, called);
}
And the memory leak is gone.
Use cases
Some use cases that would otherwise cause a memory leak and are enabled by WeakMaps include:
Keeping private data about a specific object and only giving access to it to people with a reference to the Map. A more ad-hoc approach is coming with the private-symbols proposal but that's a long time from now.
Keeping data about library objects without changing them or incurring overhead.
Keeping data about a small set of objects where many objects of the type exist to not incur problems with hidden classes JS engines use for objects of the same type.
Keeping data about host objects like DOM nodes in the browser.
Adding a capability to an object from the outside (like the event emitter example in the other answer).
Let's look at a real use
It can be used to extend an object from the outside. Let's give a practical (adapted, sort of real - to make a point) example from the real world of Node.js.
Let's say you're Node.js and you have Promise objects - now you want to keep track of all the currently rejected promises - however, you do not want to keep them from being garbage collected in case no references exist to them.
Now, you don't want to add properties to native objects for obvious reasons - so you're stuck. If you keep references to the promises you're causing a memory leak since no garbage collection can happen. If you don't keep references then you can't save additional information about individual promises. Any scheme that involves saving the ID of a promise inherently means you need a reference to it.
Enter WeakMaps
WeakMaps mean that the keys are weak. There are no ways to enumerate a weak map or to get all its values. In a weak map, you can store the data based on a key and when the key gets garbage collected so do the values.
This means that given a promise you can store state about it - and that object can still be garbage collected. Later on, if you get a reference to an object you can check if you have any state relating to it and report it.
This was used to implement unhandled rejection hooks by Petka Antonov as this:
process.on('unhandledRejection', function(reason, p) {
console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
// application specific logging, throwing an error, or other logic here
});
We keep information about promises in a map and can know when a rejected promise was handled.

This answer seems to be biased and unusable in a real world scenario. Please read it as is, and don't consider it as an actual option for anything else than experimentation
A use case could be to use it as a dictionary for listeners, I have a coworker who did that. It is very helpful because any listener is directly targetted with this way of doing things. Goodbye listener.on.
But from a more abstract point of view, WeakMap is especially powerful to dematerialize access to basically anything, you don't need a namespace to isolate its members since it is already implied by the nature of this structure. I'm pretty sure you could do some major memory improvements by replacing awkwards redundant object keys (even though deconstructing does the work for you).
Before reading what is next
I do now realize my emphasize is not exactly the best way to tackle the problem and as Benjamin Gruenbaum pointed out (check out his answer, if it's not already above mine :p), this problem could not have been solved with a regular Map, since it would have leaked, thus the main strength of WeakMap is that it does not interfere with garbage collection given that they do not keep a reference.
Here is the actual code of my coworker (thanks to him for sharing)
Full source here, it's about listeners management I talked about above (you can also take a look at the specs)
var listenableMap = new WeakMap();
export function getListenable (object) {
if (!listenableMap.has(object)) {
listenableMap.set(object, {});
}
return listenableMap.get(object);
}
export function getListeners (object, identifier) {
var listenable = getListenable(object);
listenable[identifier] = listenable[identifier] || [];
return listenable[identifier];
}
export function on (object, identifier, listener) {
var listeners = getListeners(object, identifier);
listeners.push(listener);
}
export function removeListener (object, identifier, listener) {
var listeners = getListeners(object, identifier);
var index = listeners.indexOf(listener);
if(index !== -1) {
listeners.splice(index, 1);
}
}
export function emit (object, identifier, ...args) {
var listeners = getListeners(object, identifier);
for (var listener of listeners) {
listener.apply(object, args);
}
}

WeakMap works well for encapsulation and information hiding
WeakMap is only available for ES6 and above. A WeakMap is a collection of key and value pairs where the key must be an object. In the following example, we build a WeakMap with two items:
var map = new WeakMap();
var pavloHero = {first: "Pavlo", last: "Hero"};
var gabrielFranco = {first: "Gabriel", last: "Franco"};
map.set(pavloHero, "This is Hero");
map.set(gabrielFranco, "This is Franco");
console.log(map.get(pavloHero));//This is Hero
We used the set() method to define an association between an object and another item (a string in our case). We used the get() method to retrieve the item associated with an object. The interesting aspect of the WeakMaps is the fact that it holds a weak reference to the key inside the map. A weak reference means that if the object is destroyed, the garbage collector will remove the entire entry from the WeakMap, thus freeing up memory.
var TheatreSeats = (function() {
var priv = new WeakMap();
var _ = function(instance) {
return priv.get(instance);
};
return (function() {
function TheatreSeatsConstructor() {
var privateMembers = {
seats: []
};
priv.set(this, privateMembers);
this.maxSize = 10;
}
TheatreSeatsConstructor.prototype.placePerson = function(person) {
_(this).seats.push(person);
};
TheatreSeatsConstructor.prototype.countOccupiedSeats = function() {
return _(this).seats.length;
};
TheatreSeatsConstructor.prototype.isSoldOut = function() {
return _(this).seats.length >= this.maxSize;
};
TheatreSeatsConstructor.prototype.countFreeSeats = function() {
return this.maxSize - _(this).seats.length;
};
return TheatreSeatsConstructor;
}());
})()

๐— ๐—ฒ๐˜๐—ฎ๐—ฑ๐—ฎ๐˜๐—ฎ
Weak Maps can be used to store metadata about DOM elements without interfering with garbage collection or making coworkers mad at your code. For example, you could use them to numerically index all of the elements in a webpage.
๐—ช๐—ถ๐˜๐—ต๐—ผ๐˜‚๐˜ ๐—ช๐—ฒ๐—ฎ๐—ธ๐— ๐—ฎ๐—ฝ๐˜€ ๐—ผ๐—ฟ ๐—ช๐—ฒ๐—ฎ๐—ธ๐—ฆ๐—ฒ๐˜๐˜€:
var elements = document.getElementsByTagName('*'),
i = -1, len = elements.length;
while (++i !== len) {
// Production code written this poorly makes me want to cry:
elements[i].lookupindex = i;
elements[i].elementref = [];
elements[i].elementref.push( elements[(i * i) % len] );
}
// Then, you can access the lookupindex's
// For those of you new to javascirpt, I hope the comments below help explain
// how the ternary operator (?:) works like an inline if-statement
document.write(document.body.lookupindex + '<br />' + (
(document.body.elementref.indexOf(document.currentScript) !== -1)
? // if(document.body.elementref.indexOf(document.currentScript) !== -1){
"true"
: // } else {
"false"
) // }
);
๐—จ๐˜€๐—ถ๐—ป๐—ด ๐—ช๐—ฒ๐—ฎ๐—ธ๐— ๐—ฎ๐—ฝ๐˜€ ๐—ฎ๐—ป๐—ฑ ๐—ช๐—ฒ๐—ฎ๐—ธ๐—ฆ๐—ฒ๐˜๐˜€:
var DOMref = new WeakMap(),
__DOMref_value = Array,
__DOMref_lookupindex = 0,
__DOMref_otherelement = 1,
elements = document.getElementsByTagName('*'),
i = -1, len = elements.length, cur;
while (++i !== len) {
// Production code written this well makes me want to ๐Ÿ˜Š:
cur = DOMref.get(elements[i]);
if (cur === undefined)
DOMref.set(elements[i], cur = new __DOMref_value)
cur[__DOMref_lookupindex] = i;
cur[__DOMref_otherelement] = new WeakSet();
cur[__DOMref_otherelement].add( elements[(i * i) % len] );
}
// Then, you can access the lookupindex's
cur = DOMref.get(document.body)
document.write(cur[__DOMref_lookupindex] + '<br />' + (
cur[__DOMref_otherelement].has(document.currentScript)
? // if(cur[__DOMref_otherelement].has(document.currentScript)){
"true"
: // } else {
"false"
) // }
);
๐—ง๐—ต๐—ฒ ๐——๐—ถ๐—ณ๐—ณ๐—ฒ๐—ฟ๐—ฒ๐—ป๐—ฐ๐—ฒ
The difference may look negligible, aside from the fact that the weakmap version is longer, however there is a major difference between the two pieces of code shown above. In the first snippet of code, without weak maps, the piece of code stores references every which way between the DOM elements. This prevents the DOM elements from being garbage collected. (i * i) % len may seem like an oddball that nobody would use, but think again: plenty of production code has DOM references that bounce all over the document. Now, for the second piece of code, because all the references to the elements are weak, when you a remove a node, the browser is able to determine that the node is not used (not able to be reached by your code), and thus delete it from memory. The reason for why you should be concerned about memory usage, and memory anchors (things like the first snippet of code where unused elements are held in memory) is because more memory usage means more browser GC-attempts (to try to free up memory to avert a browser crash) means slower browsing experience and sometimes a browser crash.
As for a polyfill for these, I would recommend my own library (found here # github). It is a very lightweight library that will simply polyfill it without any of the way-overly-complex frameworks you might find in other polyfills.
~ Happy coding!

I use WeakMap for the cache of worry-free memoization of functions that take in immutable objects as their parameter.
Memoization is fancy way of saying "after you compute the value, cache it so you don't have to compute it again".
Here's an example:
// using immutable.js from here https://facebook.github.io/immutable-js/
const memo = new WeakMap();
let myObj = Immutable.Map({a: 5, b: 6});
function someLongComputeFunction (someImmutableObj) {
// if we saved the value, then return it
if (memo.has(someImmutableObj)) {
console.log('used memo!');
return memo.get(someImmutableObj);
}
// else compute, set, and return
const computedValue = someImmutableObj.get('a') + someImmutableObj.get('b');
memo.set(someImmutableObj, computedValue);
console.log('computed value');
return computedValue;
}
someLongComputeFunction(myObj);
someLongComputeFunction(myObj);
someLongComputeFunction(myObj);
// reassign
myObj = Immutable.Map({a: 7, b: 8});
someLongComputeFunction(myObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>
A few things to note:
Immutable.js objects return new objects (with a new pointer) when you modify them so using them as keys in a WeakMap is guarantees the same computed value.
The WeakMap is great for memos because once the object (used as the key) gets garbage collected, so does the computed value on the WeakMap.

I have this simple feature based use case/Example for WeakMaps.
MANAGE A COLLECTION OF USERS
I started off with a User Object whose properties include a fullname, username, age, gender and a method called print which prints a human readable summary of the other properties.
/**
Basic User Object with common properties.
*/
function User(username, fullname, age, gender) {
this.username = username;
this.fullname = fullname;
this.age = age;
this.gender = gender;
this.print = () => console.log(`${this.fullname} is a ${age} year old ${gender}`);
}
I then added a Map called users to keep a collection of multiple users which are keyed by username.
/**
Collection of Users, keyed by username.
*/
var users = new Map();
Addition of the Collection also required helper functions to add, get, delete a User and even a function to print all the users for sake of completeness.
/**
Creates an User Object and adds it to the users Collection.
*/
var addUser = (username, fullname, age, gender) => {
let an_user = new User(username, fullname, age, gender);
users.set(username, an_user);
}
/**
Returns an User Object associated with the given username in the Collection.
*/
var getUser = (username) => {
return users.get(username);
}
/**
Deletes an User Object associated with the given username in the Collection.
*/
var deleteUser = (username) => {
users.delete(username);
}
/**
Prints summary of all the User Objects in the Collection.
*/
var printUsers = () => {
users.forEach((user) => {
user.print();
});
}
With all of the above code running in, say NodeJS, only the users Map has the reference to the User Objects within the entire process. There is no other reference to the individual User Objects.
Running this code an interactive NodeJS shell, just as an Example I add four users and print them:
ADD MORE INFO TO USERS WITHOUT MODIFYING EXISTING CODE
Now say a new feature is required wherein each users Social Media Platform (SMP) links need to be tracked along with the User Objects.
The key here is also that this feature must be implemented with minimum intervention to the existing code.
This is possible with WeakMaps in the following manner.
I add three separate WeakMaps for Twitter, Facebook, LinkedIn.
/*
WeakMaps for Social Media Platforms (SMPs).
Could be replaced by a single Map which can grow
dynamically based on different SMP names . . . anyway...
*/
var sm_platform_twitter = new WeakMap();
var sm_platform_facebook = new WeakMap();
var sm_platform_linkedin = new WeakMap();
A helper function, getSMPWeakMap is added simply to return the WeakMap associated with the given SMP name.
/**
Returns the WeakMap for the given SMP.
*/
var getSMPWeakMap = (sm_platform) => {
if(sm_platform == "Twitter") {
return sm_platform_twitter;
}
else if(sm_platform == "Facebook") {
return sm_platform_facebook;
}
else if(sm_platform == "LinkedIn") {
return sm_platform_linkedin;
}
return undefined;
}
A function to add a users SMP link to the given SMP WeakMap.
/**
Adds a SMP link associated with a given User. The User must be already added to the Collection.
*/
var addUserSocialMediaLink = (username, sm_platform, sm_link) => {
let user = getUser(username);
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
if(user && sm_platform_weakmap) {
sm_platform_weakmap.set(user, sm_link);
}
}
A function to print only the users who are present on the given SMP.
/**
Prints the User's fullname and corresponding SMP link of only those Users which are on the given SMP.
*/
var printSMPUsers = (sm_platform) => {
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
console.log(`Users of ${sm_platform}:`)
users.forEach((user)=>{
if(sm_platform_weakmap.has(user)) {
console.log(`\t${user.fullname} : ${sm_platform_weakmap.get(user)}`)
}
});
}
You can now add SMP links for the users, also with the possibility of each user having a link on multiple SMPs.
...continuing with the earlier Example, I add SMP links to the users, multiple links for users Bill and Sarah and then print the links for each SMP separately:
Now say a User is deleted from the users Map by calling deleteUser. That removes the only reference to the User Object. This in turn will also clear out the SMP link from any/all of the SMP WeakMaps (by Garbage Collection) as without the User Object there is no way to access any of its SMP link.
...continuing with the Example, I delete user Bill and then print out the links of the SMPs he was associated with:
There is no requirement of any additional code to individually delete the SMP link separately and the existing code before this feature was not modified in anyway.
If there is any other way to add this feature with/without WeakMaps please feel free to comment.

WEAKMAP: keep in mind weakMap is all about memory allocation and garbage collection and only related to key of object type
in javascript when u store values in key-value pair array, map, set, etc... a memory allocated to all key-value pair and this memory will not be free even if you delete or set null to that key consider this as a strongmap keys are strongly attache to memory below is example
let john = { name: "yusuf" };
let map = new Map();
map.set(yusuf, "xyz"); //here "yusuf" is the key and "xyz" is value
yusuf= null; // overwrite the reference
// the object previously referenced by yusuf is stored inside the array
// therefore it won't be garbage-collected
// we can get it using map.keys()
but this is not the case with weakMap in here memory will be free
let john = { name: "yusuf" };
let map = new WeakMap();
map.set(yusuf, "...");
yusuf= null; // overwrite the reference
// yusuf is removed from memory!
USE CASE : you will use it in javascript where u want to manage memory in more efficient way
If weโ€™re working with an object that โ€œbelongsโ€ to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive โ€“ then WeakMap is exactly whatโ€™s needed.
We put the data to a WeakMap, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well.
weakMap.set(yusuf, "secret documents");
// if yusuf dies, secret documents will be destroyed automatically
I took reference from this great article : https://javascript.info/weakmap-weakset

I think it's very helpful for checking a connection income in applications socket.
The other case, the 'Weak Collection' are useful: https://javascript.info/task/recipients-read

Related

JavaScript: reverse mapping from an object id to that object

I have a class Rectangle which has a prop called id that indicates the uniqueness of that instance of Rectangle
class Rectangle {
constructor(desc) {
this.desc = desc
this.id = Symbol()
}
}
Later I need to put the id to some other data structure and I need a quick way to look up that Rectangle instance using the id
To achieve this I need to manually create a map e.g.
const r1 = new Rectangle('first')
const r2 = new Rectangle('second')
const r3 = new Rectangle('third')
const map = {
[r1.id]: r1,
[r2.id]: r2,
[r3.id]: r3,
}
So I can do to get the reference of r1 if I need to
map[r1.id]
I wonder if there is a programmatic way to achieve this reverse mapping where id is the key and the Rectangle instance is the value?
Also, I would appreciate it if someone can suggest a better name for the map - right now the name map is not really descriptive.
Lastly, I wonder if we can get rid of id and just use the reference of the Rectangle instance? I know the answer is probably "it depends" but I think most of the time we would need an id for the instances or objects. I wonder if in what circumstances using only references is not going to cut it?
you can collect the rectangles in an array and use reduce:
const r1 = new Rectangle('first')
const r2 = new Rectangle('second')
const r3 = new Rectangle('third')
const input = [r1, r2, r3];
const map = input.reduce((output, rect) => ({...output, [rect.id]: rect}), {})
console.log(map)
If you are open to integrating external libraries into the project, one possibility is to use Lodash's keyBy method. It takes an array of objects as the first input and a function to calculate the keys of the resulting object.
If I take your example, this would be the resulting code:
import { keyBy } from 'lodash';
const r1 = new Rectangle('first');
const r2 = new Rectangle('second');
const r3 = new Rectangle('third');
const map = keyBy([r1, r2, r3], (rectangle) => rectangle.id);
You will still need to build the array itself, but this would have to be done in any case. If you want a more complex data structure, you can create a class with an internal array representation of the data, and some methods to access it. Something like this (I'm using typescript, javascript would be similar but without the typing, plus I'm not testing whether this would work just with copy and paste, there may be some minor adjustments needed):
import { keyBy, filter } from 'lodash';
class RectangleMap {
private recArray: Rectangle[];
constructor() {
this.recArray = [];
}
addRectangle(r: Rectangle): void {
this.recArray.push(r);
}
removeRectangle(r: Rectangle): void {
this.recArray = filter(this.recArray, (rec) => rec.id !== r.id);
}
getMap(key: string = 'id'): Record<string, Rectangle>ย {
return keyBy(this.recArray, rec => rec[key]);
}
}
const myRecMap = new RectangleMap();
myRecMap.addRectangle(new Rectangle('first'));
myRecMap.addRectangle(new Rectangle('second'));
myRecMap.addRectangle(new Rectangle('third'));
console.log(myRecMap.getMap());
In general, I always recommend Lodash for such array / object operations. They have very good utility functions.
I wonder if there is a programmatic way to achieve this reverse
mapping where id is the key and the Rectangle instance is the value?
If the data structure you need is a map with id as key, there is no already built way to do that without explicit key-value insertion. You probably have 2 options:
A little improvement over how you create the map:
class Rectangle {
constructor(desc) {...}
getAsMapEntry() {
return [this.id, this]
}
}
const map = new Map([
r1.getAsMapEntry(),
r2.getAsMapEntry(),
r3.getAsMapEntry()
]);
Depending on your situation, you should decide whether keeping the getAsMapEntry method inside the class Rectangle or as a utility method. For simplicity I showed the first approach, but I would probably opt for the second one. Moreover, I strongly suggest you to use Map class here, for many reasons.
You can create a new data structure that accepts Rectangles (or even better, all kinds of objects with the id property) and internally has a map. This new class is like an adapter to make the operations you need more readable at least. Do not extend the Map class because this inheritance would break the Liskov Principle, since you would have to change drastically the preconditions of the set method. The easier way would be proceeding by composition:
class EntityContainer {
// accept an array of Rectangle (or objects having the id property)
constructor(entities) {
this._internalMap = new Map(entities.map(e => [e.id, e]));
}
_assertIsEntity(entity) {
if (!entity.id)
throw "not an entity"
}
insertOrUpdate(entity) {
this._assertIsEntity(entity);
this._internalMap.set(entity.id, entity);
}
deleteByEntity(entity) {
this._assertIsEntity(entity);
this.deleteById(entity.id);
}
deleteById(id) {
this._internalMap.delete(id)
}
getById(id) {
this._internalMap.get(id);
}
}
The benefit with this approach is that you can hide the id-based logic inside the EntityContainer class, meaning that you insert entities, not id-entity pairs for example. Same goes for deletion with deleteByEntity, if you need it of course. You can validate the entities too, and you generally have more control over the operations you do, at the expense of a new class to maintain however. The drawback comes out when the method you write simply project the _internalMap method, see getById. This is not so beautiful, but in my opinion acceptable if you have to do that for just a few methods.
Also, I would appreciate it if someone can suggest a better name for
the map - right now the name map is not really descriptive.
That is something you have to decide, depending on its purpose, of course.
Lastly, I wonder if we can get rid of id and just use the reference of
the Rectangle instance? I know the answer is probably "it depends" but
I think most of the time we would need an id for the instances or
objects. I wonder if in what circumstances using only references is
not going to cut it?
Again, you did not provide us with enough information to give you an advice on whether it is more appropriate to use a reference or the id property (just to make it clear, there is no rule, as always). Based on your comment "it is part of a larger system", the id is probably a good tradeoff because it is the easier way to create relationships between multiple components, especially when an external data store is involved for instance.
I think it would be better to create 2 static properties in the Rectangle class and use them to keep track of the instances
class Rectangle {
constructor(desc) {
this.desc = desc;
this.id = Symbol();
Rectangle.instances[this.id] = this;
}
static instances = {};
static getInstance = (id) => {
return this.instances[id];
}
}
let r1 = new Rectangle('one');
let r2 = new Rectangle('two');
console.log(Rectangle.getInstance(r1.id)); // gives the r1 instance
where id is the key and the Rectangle instance is the value?
It was strangely worded(so im not sure if I understand what you mean, but it seems like using a static is best for storing created Rectangles in a data structure similar to map)
PS: if u open up inspect element, you would see the ids of these objects(The display doesn't show it though)
class Rectangle {
constructor(desc) {
this.desc = desc
this.id = Symbol(desc)
Rectangle.list[this.id]=this
}
static list={} //this is an your map(kinda)
static getRectangleById=(id)=>Rectangle.list[id]||null
//getRectangleById would return null if id is invalid
}
let r1=new Rectangle("first")
let r2=new Rectangle("second")
let r3=new Rectangle("third")
//example usage
//btw these objects do have 2 keys, just that THIS DOESNT LOG SYMBOLS
let locatedRect=Rectangle.getRectangleById(r1.id) //null if invalid
console.log(locatedRect)
let sameRect=Rectangle.list[r1.id] //undefined if invalid
console.log(sameRect)
console.log(locatedRect==sameRect)
console.log(Object.keys(sameRect)) //proof that stack snippets don't show symbols >:{
const r1 = new Rectangle('first')
const r2 = new Rectangle('second')
const r3 = new Rectangle('third')
const rectangles = [r1, r2, r3];
const map = rectangles.reduce((output, rectangle) => ({...output, [rectangle.id]: rectangle}), {})
console.log(map)
By reducing over the rectangles in the map one by one we can basically insert the rectangles using id as keys by using [rectangle.id]: rectangle.
Running example

JavaScript: Most efficient and performant way to partition ArrayBuffer memory

I would compare what I am doing to what JavaScript runtimes already do, yet I'm doing it in JavaScript and Wasm. JavaScript implementations store JavaScript objects and values in actual computer heap memory, yet performing operations such as attempting to read/write out of bounds memory don't actually modify the memory (ex: arrays perform a no-op and return undefined respectively).
I'll give an example of my specific situation:
Let's say that I have an array buffer of 1000 bytes, we'll name the variable memory.
I want to split apart the buffer specifically into Int32Arrays of size 4. Each partition from the ArrayBuffer must do two things:
a) Refer to the original buffer (so that, when the original data is manipulated, the partition will update its values automaticially)
b) Not expose the original buffer (as the partition could then be used to corrupt the other partitions)
I have a function that determines which section is available for usage, we'll call it findPartition. It returns an integer acting as a pointer to a set of available bytes. (like C's malloc)
Each partition is expected to always remain the same type, that is, they will always be Int32Arrays if they start as an Int32Array, and their size will always be constant.
The script operating on the partition may both, write to, and read from, its partitioned array.
Originally, I was thinking that I could just call the Int32Array constructor on my array buffer, simply like so:
const createPartition = () => new Int32Array( memory, findPartition(), 4 );
The problem is that the buffer is exposed, so I could either delete the buffer property.
But... the buffer property is readonly, so delete fails when used on the array.
I then thought that I could make a class to do this:
class Partition {
#source = new Int32Array( memory, findPartition(), 4 );
get 0() { return this.#source[0]; }
set 0(x) { this.#source[0] = x; }
get 1() { return this.#source[1]; }
set 1(x) { this.#source[1] = x; }
get 2() { return this.#source[2]; }
set 2(x) { this.#source[2] = x; }
get 3() { return this.#source[3]; }
set 3(x) { this.#source[3] = x; }
get length() { return 4; }
};
Well, that works, but it's much more verbose, thus harder to maintain later, and, as the partitions are not given direct access to the indexes' values, because they have to go through getters and setters, I feel that performance could be lost.
Ideally, the Int32Array.prototype is also on the object, so I would have to wrap everything, which would be annoying and unmaintainable. If the spec updates the methods of the prototype, then I would have to update the wrappers too.
Does anyone have a better way to segment the array buffer, while maintaining safety between the segments?
Simplest way is to extend chosen typed array like that:
// Seal and freeze hidden Object (TypedArray.prototype) that has methods
// that can leak original buffer if attacked using defineProperty tricks
// Since we can't directly access hidden Object on which `subarray`
// and many other methods defined we use this workaround
// Paranoid: More checks required to make sure that: `subarray` method; 'byteOffset',
// 'byteLength', 'buffer' getters; are not modified beforehand
Object.seal(Int8Array.__proto__.prototype);
Object.freeze(Int8Array.__proto__.prototype);
class customUint32Array extends Uint32Array {
get buffer(){
// copy! viewed array buffer segment
// test if `super.` is faster/slower than `this.` access
return super.buffer.slice(this.byteOffset, this.byteOffset + this.byteLength);
// return super.buffer.slice(super.byteOffset, super.byteOffset + super.byteLength);
}
}
// var customUint32ArrayOverWholeBufferCached = new customUint32Array(memory);
function Partition(){
// test performance of `new` vs `customUint32ArrayOverWholeBufferCached.subarray`
// for fastest array buffer view creation
return new customUint32Array(memory, findPartitionByteOffset(), 4);
// return customUint32ArrayOverWholeBufferCached.subarray(findPartitionIndex(), 4);
}
By the way 'private' class properties in most JS environments are exposed as any other property and will leak original buffer.
Prototype chains forged manually instead of class X extends Y are welcome in comments.
If one will pass my original buffer leak tests, I'll include it here.
Current instance' prototype chain looks something like: customUint32Array.Uint32Array.TypedArray.prototype.Object

WeakMap, WeakSet, what's the point? [duplicate]

What are the actual uses of the WeakMap data structure introduced in ECMAScript 6?
Since a key of a weak map creates a strong reference to its corresponding value, ensuring that a value which has been inserted into a weak map will never disappear as long as its key is still alive, it can't be used for memo tables, caches or anything else that you would normally use weak references, maps with weak values, etc. for.
It seems to me that this:
weakmap.set(key, value);
...is just a roundabout way of saying this:
key.value = value;
What concrete use cases am I missing?
Fundamentally
WeakMaps provide a way to extend objects from the outside without interfering with garbage collection. Whenever you want to extend an object but can't because it is sealed - or from an external source - a WeakMap can be applied.
A WeakMap is a map (dictionary) where the keys are weak - that is, if all references to the key are lost and there are no more references to the value - the value can be garbage collected. Let's show this first through examples, then explain it a bit and finally finish with real use.
Let's say I'm using an API that gives me a certain object:
var obj = getObjectFromLibrary();
Now, I have a method that uses the object:
function useObj(obj){
doSomethingWith(obj);
}
I want to keep track of how many times the method was called with a certain object and report if it happens more than N times. Naively one would think to use a Map:
var map = new Map(); // maps can have object keys
function useObj(obj){
doSomethingWith(obj);
var called = map.get(obj) || 0;
called++; // called one more time
if(called > 10) report(); // Report called more than 10 times
map.set(obj, called);
}
This works, but it has a memory leak - we now keep track of every single library object passed to the function which keeps the library objects from ever being garbage collected. Instead - we can use a WeakMap:
var map = new WeakMap(); // create a weak map
function useObj(obj){
doSomethingWith(obj);
var called = map.get(obj) || 0;
called++; // called one more time
if(called > 10) report(); // Report called more than 10 times
map.set(obj, called);
}
And the memory leak is gone.
Use cases
Some use cases that would otherwise cause a memory leak and are enabled by WeakMaps include:
Keeping private data about a specific object and only giving access to it to people with a reference to the Map. A more ad-hoc approach is coming with the private-symbols proposal but that's a long time from now.
Keeping data about library objects without changing them or incurring overhead.
Keeping data about a small set of objects where many objects of the type exist to not incur problems with hidden classes JS engines use for objects of the same type.
Keeping data about host objects like DOM nodes in the browser.
Adding a capability to an object from the outside (like the event emitter example in the other answer).
Let's look at a real use
It can be used to extend an object from the outside. Let's give a practical (adapted, sort of real - to make a point) example from the real world of Node.js.
Let's say you're Node.js and you have Promise objects - now you want to keep track of all the currently rejected promises - however, you do not want to keep them from being garbage collected in case no references exist to them.
Now, you don't want to add properties to native objects for obvious reasons - so you're stuck. If you keep references to the promises you're causing a memory leak since no garbage collection can happen. If you don't keep references then you can't save additional information about individual promises. Any scheme that involves saving the ID of a promise inherently means you need a reference to it.
Enter WeakMaps
WeakMaps mean that the keys are weak. There are no ways to enumerate a weak map or to get all its values. In a weak map, you can store the data based on a key and when the key gets garbage collected so do the values.
This means that given a promise you can store state about it - and that object can still be garbage collected. Later on, if you get a reference to an object you can check if you have any state relating to it and report it.
This was used to implement unhandled rejection hooks by Petka Antonov as this:
process.on('unhandledRejection', function(reason, p) {
console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
// application specific logging, throwing an error, or other logic here
});
We keep information about promises in a map and can know when a rejected promise was handled.
This answer seems to be biased and unusable in a real world scenario. Please read it as is, and don't consider it as an actual option for anything else than experimentation
A use case could be to use it as a dictionary for listeners, I have a coworker who did that. It is very helpful because any listener is directly targetted with this way of doing things. Goodbye listener.on.
But from a more abstract point of view, WeakMap is especially powerful to dematerialize access to basically anything, you don't need a namespace to isolate its members since it is already implied by the nature of this structure. I'm pretty sure you could do some major memory improvements by replacing awkwards redundant object keys (even though deconstructing does the work for you).
Before reading what is next
I do now realize my emphasize is not exactly the best way to tackle the problem and as Benjamin Gruenbaum pointed out (check out his answer, if it's not already above mine :p), this problem could not have been solved with a regular Map, since it would have leaked, thus the main strength of WeakMap is that it does not interfere with garbage collection given that they do not keep a reference.
Here is the actual code of my coworker (thanks to him for sharing)
Full source here, it's about listeners management I talked about above (you can also take a look at the specs)
var listenableMap = new WeakMap();
export function getListenable (object) {
if (!listenableMap.has(object)) {
listenableMap.set(object, {});
}
return listenableMap.get(object);
}
export function getListeners (object, identifier) {
var listenable = getListenable(object);
listenable[identifier] = listenable[identifier] || [];
return listenable[identifier];
}
export function on (object, identifier, listener) {
var listeners = getListeners(object, identifier);
listeners.push(listener);
}
export function removeListener (object, identifier, listener) {
var listeners = getListeners(object, identifier);
var index = listeners.indexOf(listener);
if(index !== -1) {
listeners.splice(index, 1);
}
}
export function emit (object, identifier, ...args) {
var listeners = getListeners(object, identifier);
for (var listener of listeners) {
listener.apply(object, args);
}
}
WeakMap works well for encapsulation and information hiding
WeakMap is only available for ES6 and above. A WeakMap is a collection of key and value pairs where the key must be an object. In the following example, we build a WeakMap with two items:
var map = new WeakMap();
var pavloHero = {first: "Pavlo", last: "Hero"};
var gabrielFranco = {first: "Gabriel", last: "Franco"};
map.set(pavloHero, "This is Hero");
map.set(gabrielFranco, "This is Franco");
console.log(map.get(pavloHero));//This is Hero
We used the set() method to define an association between an object and another item (a string in our case). We used the get() method to retrieve the item associated with an object. The interesting aspect of the WeakMaps is the fact that it holds a weak reference to the key inside the map. A weak reference means that if the object is destroyed, the garbage collector will remove the entire entry from the WeakMap, thus freeing up memory.
var TheatreSeats = (function() {
var priv = new WeakMap();
var _ = function(instance) {
return priv.get(instance);
};
return (function() {
function TheatreSeatsConstructor() {
var privateMembers = {
seats: []
};
priv.set(this, privateMembers);
this.maxSize = 10;
}
TheatreSeatsConstructor.prototype.placePerson = function(person) {
_(this).seats.push(person);
};
TheatreSeatsConstructor.prototype.countOccupiedSeats = function() {
return _(this).seats.length;
};
TheatreSeatsConstructor.prototype.isSoldOut = function() {
return _(this).seats.length >= this.maxSize;
};
TheatreSeatsConstructor.prototype.countFreeSeats = function() {
return this.maxSize - _(this).seats.length;
};
return TheatreSeatsConstructor;
}());
})()
๐— ๐—ฒ๐˜๐—ฎ๐—ฑ๐—ฎ๐˜๐—ฎ
Weak Maps can be used to store metadata about DOM elements without interfering with garbage collection or making coworkers mad at your code. For example, you could use them to numerically index all of the elements in a webpage.
๐—ช๐—ถ๐˜๐—ต๐—ผ๐˜‚๐˜ ๐—ช๐—ฒ๐—ฎ๐—ธ๐— ๐—ฎ๐—ฝ๐˜€ ๐—ผ๐—ฟ ๐—ช๐—ฒ๐—ฎ๐—ธ๐—ฆ๐—ฒ๐˜๐˜€:
var elements = document.getElementsByTagName('*'),
i = -1, len = elements.length;
while (++i !== len) {
// Production code written this poorly makes me want to cry:
elements[i].lookupindex = i;
elements[i].elementref = [];
elements[i].elementref.push( elements[(i * i) % len] );
}
// Then, you can access the lookupindex's
// For those of you new to javascirpt, I hope the comments below help explain
// how the ternary operator (?:) works like an inline if-statement
document.write(document.body.lookupindex + '<br />' + (
(document.body.elementref.indexOf(document.currentScript) !== -1)
? // if(document.body.elementref.indexOf(document.currentScript) !== -1){
"true"
: // } else {
"false"
) // }
);
๐—จ๐˜€๐—ถ๐—ป๐—ด ๐—ช๐—ฒ๐—ฎ๐—ธ๐— ๐—ฎ๐—ฝ๐˜€ ๐—ฎ๐—ป๐—ฑ ๐—ช๐—ฒ๐—ฎ๐—ธ๐—ฆ๐—ฒ๐˜๐˜€:
var DOMref = new WeakMap(),
__DOMref_value = Array,
__DOMref_lookupindex = 0,
__DOMref_otherelement = 1,
elements = document.getElementsByTagName('*'),
i = -1, len = elements.length, cur;
while (++i !== len) {
// Production code written this well makes me want to ๐Ÿ˜Š:
cur = DOMref.get(elements[i]);
if (cur === undefined)
DOMref.set(elements[i], cur = new __DOMref_value)
cur[__DOMref_lookupindex] = i;
cur[__DOMref_otherelement] = new WeakSet();
cur[__DOMref_otherelement].add( elements[(i * i) % len] );
}
// Then, you can access the lookupindex's
cur = DOMref.get(document.body)
document.write(cur[__DOMref_lookupindex] + '<br />' + (
cur[__DOMref_otherelement].has(document.currentScript)
? // if(cur[__DOMref_otherelement].has(document.currentScript)){
"true"
: // } else {
"false"
) // }
);
๐—ง๐—ต๐—ฒ ๐——๐—ถ๐—ณ๐—ณ๐—ฒ๐—ฟ๐—ฒ๐—ป๐—ฐ๐—ฒ
The difference may look negligible, aside from the fact that the weakmap version is longer, however there is a major difference between the two pieces of code shown above. In the first snippet of code, without weak maps, the piece of code stores references every which way between the DOM elements. This prevents the DOM elements from being garbage collected. (i * i) % len may seem like an oddball that nobody would use, but think again: plenty of production code has DOM references that bounce all over the document. Now, for the second piece of code, because all the references to the elements are weak, when you a remove a node, the browser is able to determine that the node is not used (not able to be reached by your code), and thus delete it from memory. The reason for why you should be concerned about memory usage, and memory anchors (things like the first snippet of code where unused elements are held in memory) is because more memory usage means more browser GC-attempts (to try to free up memory to avert a browser crash) means slower browsing experience and sometimes a browser crash.
As for a polyfill for these, I would recommend my own library (found here # github). It is a very lightweight library that will simply polyfill it without any of the way-overly-complex frameworks you might find in other polyfills.
~ Happy coding!
I use WeakMap for the cache of worry-free memoization of functions that take in immutable objects as their parameter.
Memoization is fancy way of saying "after you compute the value, cache it so you don't have to compute it again".
Here's an example:
// using immutable.js from here https://facebook.github.io/immutable-js/
const memo = new WeakMap();
let myObj = Immutable.Map({a: 5, b: 6});
function someLongComputeFunction (someImmutableObj) {
// if we saved the value, then return it
if (memo.has(someImmutableObj)) {
console.log('used memo!');
return memo.get(someImmutableObj);
}
// else compute, set, and return
const computedValue = someImmutableObj.get('a') + someImmutableObj.get('b');
memo.set(someImmutableObj, computedValue);
console.log('computed value');
return computedValue;
}
someLongComputeFunction(myObj);
someLongComputeFunction(myObj);
someLongComputeFunction(myObj);
// reassign
myObj = Immutable.Map({a: 7, b: 8});
someLongComputeFunction(myObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>
A few things to note:
Immutable.js objects return new objects (with a new pointer) when you modify them so using them as keys in a WeakMap is guarantees the same computed value.
The WeakMap is great for memos because once the object (used as the key) gets garbage collected, so does the computed value on the WeakMap.
I have this simple feature based use case/Example for WeakMaps.
MANAGE A COLLECTION OF USERS
I started off with a User Object whose properties include a fullname, username, age, gender and a method called print which prints a human readable summary of the other properties.
/**
Basic User Object with common properties.
*/
function User(username, fullname, age, gender) {
this.username = username;
this.fullname = fullname;
this.age = age;
this.gender = gender;
this.print = () => console.log(`${this.fullname} is a ${age} year old ${gender}`);
}
I then added a Map called users to keep a collection of multiple users which are keyed by username.
/**
Collection of Users, keyed by username.
*/
var users = new Map();
Addition of the Collection also required helper functions to add, get, delete a User and even a function to print all the users for sake of completeness.
/**
Creates an User Object and adds it to the users Collection.
*/
var addUser = (username, fullname, age, gender) => {
let an_user = new User(username, fullname, age, gender);
users.set(username, an_user);
}
/**
Returns an User Object associated with the given username in the Collection.
*/
var getUser = (username) => {
return users.get(username);
}
/**
Deletes an User Object associated with the given username in the Collection.
*/
var deleteUser = (username) => {
users.delete(username);
}
/**
Prints summary of all the User Objects in the Collection.
*/
var printUsers = () => {
users.forEach((user) => {
user.print();
});
}
With all of the above code running in, say NodeJS, only the users Map has the reference to the User Objects within the entire process. There is no other reference to the individual User Objects.
Running this code an interactive NodeJS shell, just as an Example I add four users and print them:
ADD MORE INFO TO USERS WITHOUT MODIFYING EXISTING CODE
Now say a new feature is required wherein each users Social Media Platform (SMP) links need to be tracked along with the User Objects.
The key here is also that this feature must be implemented with minimum intervention to the existing code.
This is possible with WeakMaps in the following manner.
I add three separate WeakMaps for Twitter, Facebook, LinkedIn.
/*
WeakMaps for Social Media Platforms (SMPs).
Could be replaced by a single Map which can grow
dynamically based on different SMP names . . . anyway...
*/
var sm_platform_twitter = new WeakMap();
var sm_platform_facebook = new WeakMap();
var sm_platform_linkedin = new WeakMap();
A helper function, getSMPWeakMap is added simply to return the WeakMap associated with the given SMP name.
/**
Returns the WeakMap for the given SMP.
*/
var getSMPWeakMap = (sm_platform) => {
if(sm_platform == "Twitter") {
return sm_platform_twitter;
}
else if(sm_platform == "Facebook") {
return sm_platform_facebook;
}
else if(sm_platform == "LinkedIn") {
return sm_platform_linkedin;
}
return undefined;
}
A function to add a users SMP link to the given SMP WeakMap.
/**
Adds a SMP link associated with a given User. The User must be already added to the Collection.
*/
var addUserSocialMediaLink = (username, sm_platform, sm_link) => {
let user = getUser(username);
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
if(user && sm_platform_weakmap) {
sm_platform_weakmap.set(user, sm_link);
}
}
A function to print only the users who are present on the given SMP.
/**
Prints the User's fullname and corresponding SMP link of only those Users which are on the given SMP.
*/
var printSMPUsers = (sm_platform) => {
let sm_platform_weakmap = getSMPWeakMap(sm_platform);
console.log(`Users of ${sm_platform}:`)
users.forEach((user)=>{
if(sm_platform_weakmap.has(user)) {
console.log(`\t${user.fullname} : ${sm_platform_weakmap.get(user)}`)
}
});
}
You can now add SMP links for the users, also with the possibility of each user having a link on multiple SMPs.
...continuing with the earlier Example, I add SMP links to the users, multiple links for users Bill and Sarah and then print the links for each SMP separately:
Now say a User is deleted from the users Map by calling deleteUser. That removes the only reference to the User Object. This in turn will also clear out the SMP link from any/all of the SMP WeakMaps (by Garbage Collection) as without the User Object there is no way to access any of its SMP link.
...continuing with the Example, I delete user Bill and then print out the links of the SMPs he was associated with:
There is no requirement of any additional code to individually delete the SMP link separately and the existing code before this feature was not modified in anyway.
If there is any other way to add this feature with/without WeakMaps please feel free to comment.
WEAKMAP: keep in mind weakMap is all about memory allocation and garbage collection and only related to key of object type
in javascript when u store values in key-value pair array, map, set, etc... a memory allocated to all key-value pair and this memory will not be free even if you delete or set null to that key consider this as a strongmap keys are strongly attache to memory below is example
let john = { name: "yusuf" };
let map = new Map();
map.set(yusuf, "xyz"); //here "yusuf" is the key and "xyz" is value
yusuf= null; // overwrite the reference
// the object previously referenced by yusuf is stored inside the array
// therefore it won't be garbage-collected
// we can get it using map.keys()
but this is not the case with weakMap in here memory will be free
let john = { name: "yusuf" };
let map = new WeakMap();
map.set(yusuf, "...");
yusuf= null; // overwrite the reference
// yusuf is removed from memory!
USE CASE : you will use it in javascript where u want to manage memory in more efficient way
If weโ€™re working with an object that โ€œbelongsโ€ to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive โ€“ then WeakMap is exactly whatโ€™s needed.
We put the data to a WeakMap, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well.
weakMap.set(yusuf, "secret documents");
// if yusuf dies, secret documents will be destroyed automatically
I took reference from this great article : https://javascript.info/weakmap-weakset
I think it's very helpful for checking a connection income in applications socket.
The other case, the 'Weak Collection' are useful: https://javascript.info/task/recipients-read

Efficient memoization of object arguments

Summary: Is there a faster way to hash objects than JSON.stringify?
Details: I have a Ruby and JavaScript library (NeatJSON) that provides pretty-printing of JavaScript values. I recently fixed a problem where deeply-nested objects caused O(n!) performance (n being the nesting level) using memoization based on the object being serialized and the indentation amount.
In Ruby, the fix was really easy, because you can index hashes by arrays of unique sets of objects:
build = ->(object,indent) do
memoizer[[object,indent]] ||= <all the rest of the code>
end
In JavaScript, however, I can't index an object by another object (in a unique way). Following the lead of several articles I found online, I decide to fix the problem generically, using JSON.stringify on the full set of arguments to the function to create a unique key for memoization:
function memoize(f){
var memo = {};
var slice = Array.prototype.slice;
return function(){
var args = slice.call(arguments);
var mkey = JSON.stringify(args);
if (!(mkey in memo)) memo[mkey] = f.apply(this,args);
return memo[mkey];
}
}
function rawBuild(o,indent){ .. }
var build = memoize(rawBuild);
This works, but (a) it's a little slower than I'd like, and (b) it seems wildly inefficient (and inelegant) to perform (naive) serialization of every object and value that I'm about to serialize smartly. The act of serializing a large object with many values is going to store a string and formatting result for EVERY unique value (not just leaf values) in the entire object.
Is there a modern JavaScript trick that would let me uniquely identify a value? For example, some way of accessing an internal ID, or otherwise associating complex objects with unique integers that takes O(1) time to find the identifier for a value?
If you are looking to memoise your objects by identity (not by content), then you'll want to use a WeakMap which is designed for exactly this purpose. They don't work for primitive values though, so you'll need a different solution for such arguments.
Using #Bergi's suggestion of a WeakMap I found out about Map, which allows using any value type as the key (not just objects). Because I needed a compound keyโ€”uniquely memoizing the combination of the value passed in and the indentation stringโ€”I created a hierarchical memoization structure:
function memoizedBuild(){
var memo = new Map;
return function(value,indent){
var byIndent=memo.get(value);
if (!byIndent) memo.set(value,byIndent={});
if (!byIndent[indent]) byIndent[indent] = rawBuild(value,indent);
return byIndent[indent];
}
}
This proved to be about 4ร— faster than the memoization code I had been using when serializing a large 270kB JSON object.
Note that in the above code I'm able to use !byIndent[indent] only because I know that rawBuild will never return a falsey value (null, undefined, false, NaN, 0, ""). The safer code line would look something like:
if (!(indent in byIndent)) byIndent[indent] = rawBuild(value,indent);
If you just need to memoise objects then it makes sense to assign some unique ID to your objects .
var gID = 0;
function createNode() {
var obj = ...
obj.id = (++gID).toString();
}
and use those obj.id's as keys in your memo collection.
That would be fastest and least greedy solution.
Update:
If you want that id property to do not clash with existing properties
then you can create non-enumerable properties using standard ES5.1 Object.createProperty() (with some unique name) or to use ES6 symbols:
var gID = 0;
var gUidSym = Symbol("uid");
function getUidOf(obj) {
return obj[gUidSym]
|| (obj[gUidSym] = (++gID).toString());
}

Immutable Hash and Array implementation in JavaScript?

Is there simple immutable hash and array implementation in javascript? I don't need best speed, a reasonable speed better than a clone would be good.
Also, if there are simple implementations in Java or some other languages that can be easily understood and ported to JavaScript, it would be also nice.
UPDATE:
The goal isn't to just froze the hash (or array), but to make an efficient implementation of update operation - update of immutable hash should return a new immutable hash. And it should be more efficient than doing it by "clone original and update it".
Native JS types have complexity of update something like O(1), with cloning the complexity will be O(n), with special immutable data structures (what I asked for) it will be 0(log(n))
UPDATE2: JavaScript already has Array / Hash :
Yes, but they are mutable, I need something similar but immutable, basically it can be done very simply by cloning hash2 = hash1.clone(); hash2[key] = value but it's very inefficient, there are algorithms that made it very efficient, without using the clone.
hash1 = {}
hash2 = hash1.set('key', 'value2')
hash3 = hash1.set('key', 'value3)
console.log(hash1) // => {}
console.log(hash2) // => {key: 'value2'}
console.log(hash3) // => {key: 'value3'}
SOLUTION:
It's not an implementation for immutable hash, but more like a hack for my current problem, maybe it also helps someone.
A little more about why I need immutable data structures - I use Node.js and sort of in-memory database. One request can read database, other update it - update can take a lot of time (calling remote services) - so I can't block all read processes and wait until update will be finished, also update may fail and database should be rolled back. So I need to somehow isolate (ACID) read and write operations to the in-memory database.
That's why I need immutable arrays and hashes - to implement sort of MVCC. But it seems there is a simpler way to do it. Instead of updating database directly - the update operation just records changes to database (but not perform it directly) - in form of "add 42 to array db.someArray".
In the end - the product of update operation will be an array of such change commands, and because it can be applied very quickly - we can block the database to apply it.
But, still it will be interesting to see if there are implementation of immutable data structures in javascript, so I'll leave this question open.
I know this question is old but I thought people that were searching like me should be pointed to Facebook's Immutable.js which offers many different types of immutable data structures in a very efficient way.
I had the same requirements for persistent data structures for JS, so a while ago I made an implementation of a persistent map.. https://github.com/josef-jelinek/cofy/blob/master/lang/feat.js
It contains implementation of a balanced tree based (sorted) map, and a naive copy-on-write map (and unfinished persistent vector/array).
var map = FEAT.map();
var map1 = map.assoc('key', 'value');
var value = map1.get('key');
var map2 = map1.dissoc('key');
...
it supports other methods like count(), contains(key), keys(into = []), values(into = []), toObject(into = {}), toString()
The implementation is not too complicated and it is in public domain. I accept suggestions and contributors too :).
Update: you can find unit tests (examples of usage) at https://github.com/josef-jelinek/cofy/blob/master/tests/test-feat.html
Update 2: Persistent vector implementation is now there as well with the following operations: count(), get(i), set(i, value), push(value), pop(), toArray(into = []), toString()
The only way to make an object immutable is to hide it inside a function. You can then use the function to return either the default hash or an updated version, but you can't actually store an immutable hash in the global scope.
function my_hash(delta) {
var default = {mykey: myvalue};
if (delta) {
for (var key, value in delta) {
if (default.hasOwnProperty(key)) default[key] = value;
}
}
return default;
}
I don't think this is a good idea though.
The best way to clone an object in Javascript I'm aware of, is the one contained in underscore.js
Shortly:
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};

Categories

Resources