Understanding weak maps - javascript

ECMAScript 6 introduces weak maps, available in Node.JS v0.11.3 with the --harmony flag. Consider the following.
let weakMap = WeakMap();
let key = [];
let rubbish = 'fish cans';
weakMap.set(key, rubbish);
rubbish = 'empty bottle';
// Prints "fish cans"
console.log(weakMap.get(key));
I was under the impression that, for weak maps, the reference from the key to the value is weak, so that if the only reference to the value is the key, then the value can no longer be accessed.
Why then is the value 'fish cans' still accessible and not garbage collected? The variable rubbish no longer references it, and the reference from key to 'fish cans' is weak, i.e. non-existant from the point of view of the garbage collector. What am I missing?

The weak part is about the keys, not the values. From the current draft:
WeakMap are intended to provide a mechanism for dynamically associating state with an object in a manner that does not “leak” memory resources if, in the absence of the WeakMap, the object otherwise became inaccessible and subject to resource reclamation by the implementation’s garbage collection mechanisms.
Say you have a DOM element and want to associate some data with it and use a WeakMap for that: weakMap.set(domElement, data);. When the DOM element gets deleted then the entry in the weak map gets deleted too.
On the other hand you would not want the data to be deleted as long the DOM element exists, just because there is no other reference to it outside the weak map.
Apart from that 'fish cans' is a primitive type and as such not subject to the garbage collection.

Why then is the value 'fish cans' still accessible and not garbage collected? The variable rubbish no longer references it, and the reference from key to 'fish cans' is weak, i.e. non-existant from the point of view of the garbage collector. What am I missing?
The variable rubbish never was the one that needed to reference it. The association (reference) from the key to the value still exists as long as the key is not collected. The weak reference, which the GC cannot see, is the one from the map to the key/value pair (the one which would make the map enumerable). Yet your key still exists, and you can get every value that you stored for it in the map:
var map = WeakMap(),
key = [];
map.set(key, 'fish cans');
console.log(map.get(key)); // Prints "fish cans"
To demonstrate the weakness, you'd have to use the following:
var map = WeakMap(),
key = [];
map.set(key, 'fish cans');
// map.size == 1
key = null;
// map.size == 0 - the fish cans got garbage-collected together with the key
map.get(???) // impossible now

I am trying to understand this as well. I think the paragraph said like:
var key={a:1};
var wm=new WeakMap();
wm.set(key,"value");
wm.get(key); //you can get "value";
key=undefined;
console.log(wm.get(key));//error.original key have no longer existed. so "value" is not longer existed as well.
Due to WeakMap has no way to retrieve the keys, you have no way to get that "value" any more.
if we use Map(), due to has keys(),entries(), although the variable key lost reference, "value" still can be retrieved.
var key={a:1};
var m=new Map();
m.set(key,"value");
console.log(m.keys().next());; //you can get "value";
key=undefined;
console.log(m.get(m.keys().next().value));//you can get "value";
Conclusion:
If object key of WeakMap have lost its reference, this key and value will be garbage collected as well(because logically inaccessible). This way can prevent memory leak.
If object key of Map have lost its reference, this key and value will be still available until you deleted or cleared. So you need to be aware of memory leak during development
Just find a link about understanding weakmap and its use-cases:
http://ilikekillnerds.com/2015/02/what-are-weakmaps-in-es6/

I'm adding this answer to address/clarify the following point:
The initial value of the variable 'rubbish' was never mutated; all you accomplished on rubbish = 'empty bottle'; was to associate the alias rubbish to a different value. Hence even after rubbish = 'empty bottle'; was executed, the weakMap entry with key 'key' still has a value associated with it that references the original value of rubbish.
Then to make the value lose its association with the key, you should change value of the key variable (as already noted in #Bergi's answer).
Note that key.push("whatever"); does not amount to a change. However, key = []; repeated again would do as well as key = null;. Generally, any reassignment will amount to the desired change.

Related

Is it safe to rename the keys of an object while iterating them?

I'm renaming the keys of an object while iterating them:
Object.keys(object).forEach(function(oldKey) {
var newKey = someFunc(oldKey);
if (newKey !== oldKey) {
object[newKey] = object[oldKey];
delete object[oldKey];
}
}
And I would like to know if this method is safe.
In other words, can I be sure that my code will never iterate a key which has been renamed in a previous iteration?
No, you aren't safe. You're mutating the object live, based on an array that is not live. If you happen to cross a new name with an old (rename a to b, but b already exists and haven't been reached yet) you're going to have a bad time.
You will not come across keys you've already seen, but you have no way to know whether the newKey is not already found in the object.
There are workarounds, the situation is similar to .splice()ing an array (removing elements) while you iterate it, and the simple workaround is to iterate backwards, so that you always already pass the altered keys. (Or in your case, checking with the in operator)
You're much better, however, creating and returning a new object:
const newObj = Object.keys(object).reduce(function(result, oldKey) {
var newKey = someFunc(oldKey);
return { ...result, [newKey]: object[oldKey] };
}, {});
You get a lot of things for free when you treat all of your data structures as immutables (and more specifically, when the keys never change)
Object.keys, like many other methods, returns an Array that you can iterate over. This Array is not "live" but a snapshot from the time of taking it (e.g. executing Object.keys). So yes, you're save to use it as intended.
There are very little examples of methods that return "live lists" instead of an Array; I guess you're having NodeLists in mind, that you'll get when using document.querySelectorAll. This however not an Array but a NodeList.
However, there may be one pitfall I can see is: When a generated newKey already exists in the list of oldKeys (not the current one!). So you may or may not (depending on the position in the array) iterate over the already overwritten new key.
Here is a solution to change the key without creating a new Object.
for(key in obj){
Object.defineProperty(obj, `myNewName`, Object.getOwnPropertyDescriptor(obj, key));
delete obj[key];
}

What data type are JavaScript object references and what is the most efficient way to store them?

Let's say we have this object: var a = {x: 3}.
Now, if we have an array arr = [a] that holds a reference to this object, what arr[0] actually stores is a reference to that object, not the actual object data.
I have many objects (20k+) such as a that I want to keep track of, probably creating an array similar to arr each second. As I want the memory allocation to be as efficient as possible, can I somehow tell the compiler that my array will only contain references to objects like a? I thought of using something like TypedArray, but I don't know what type the reference of a is, I guess I can't just use new UInt32Array() and actually store a at each index.
In languages like C++ you could have an array of pointers and you always know the size of a pointer (eg: 8 bytes on 64bit machines).
Is there any way to efficiently store references/pointers to objects in an Array or Object?
I think this answers my own questions. I can create an initial Array, add the elements that I want to it and then whenever I need a new array, just copy this one and update the elements. This way it doesn't matter what data type are the references, as I can directly allocate exactly the entire memory needed for the array.
Example in a pseudo-JavaScript:
var initialArray = [];
// push initial references into this array
// whenever I need a new array do:
var newArray = initialArray.slice();
// update references in the newArray
...
for i in newArray
newArray[i] = newRefi;
...
This way the newArray will be the correct size when created.
LE: Although this works in theory, it actually makes performance worse, probably because now creating the newArray now has to copy memory and do other crazy stuff instead of just allocating some memory.

What's the difference between ES6 Map and WeakMap?

Looking this and this MDN pages it seems like the only difference between Maps and WeakMaps is a missing "size" property for WeakMaps. But is this true? What's the difference between them?
They both behave differently when a object referenced by their keys/values gets deleted. Lets take the below example code:
var map = new Map();
var weakmap = new WeakMap();
(function(){
var a = {x: 12};
var b = {y: 12};
map.set(a, 1);
weakmap.set(b, 2);
})()
The above IIFE is executed there is no way we can reference {x: 12} and {y: 12} anymore. Garbage collector goes ahead and deletes the key b pointer from “WeakMap” and also removes {y: 12} from memory. But in case of “Map”, the garbage collector doesn’t remove a pointer from “Map” and also doesn’t remove {x: 12} from memory.
Summary: WeakMap allows garbage collector to do its task but not Map.
References: http://qnimate.com/difference-between-map-and-weakmap-in-javascript/
Maybe the next explanation will be more clear for someone.
var k1 = {a: 1};
var k2 = {b: 2};
var map = new Map();
var wm = new WeakMap();
map.set(k1, 'k1');
wm.set(k2, 'k2');
k1 = null;
map.forEach(function (val, key) {
console.log(key, val); // k1 {a: 1}
});
k2 = null;
wm.get(k2); // undefined
As you see, after removing k1 key from the memory we can still access it inside the map. At the same time removing k2 key of WeakMap removes it from wm as well by reference.
That's why WeakMap hasn't enumerable methods like forEach, because there is no such thing as list of WeakMap keys, they are just references to another objects.
From the very same page, section "Why Weak Map?":
The experienced JavaScript programmer will notice that this API could
be implemented in JavaScript with two arrays (one for keys, one for
values) shared by the 4 API methods. Such an implementation would have
two main inconveniences. The first one is an O(n) search (n being the
number of keys in the map). The second one is a memory leak issue.
With manually written maps, the array of keys would keep references to
key objects, preventing them from being garbage collected. In native
WeakMaps, references to key objects are held "weakly", which means
that they do not prevent garbage collection in case there would be no
other reference to the object.
Because of references being weak, WeakMap keys are not enumerable
(i.e. there is no method giving you a list of the keys). If they were,
the list would depend on the state of garbage collection, introducing
non-determinism.
[And that's why they have no size property as well]
If you want to have a list of keys, you should
maintain it yourself. There is also an ECMAScript
proposal
aiming at introducing simple sets and maps which would not use weak
references and would be enumerable.
‐ which would be the "normal" Maps. Not mentioned at MDN, but in the harmony proposal, those also have items, keys and values generator methods and implement the Iterator interface.
Another difference (source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap):
Keys of WeakMaps are of the type Object only. Primitive data types as
keys are not allowed (e.g. a Symbol can't be a WeakMap key).
Nor can a string, number, or boolean be used as a WeakMap key. A Map can use primitive values for keys.
w = new WeakMap;
w.set('a', 'b'); // Uncaught TypeError: Invalid value used as weak map key
m = new Map
m.set('a', 'b'); // Works
From Javascript.info
Map -- If we use an object as the key in a regular Map, then while the Map exists, that object exists as well. It occupies memory and may not be garbage collected.
let john = { name: "John" };
let array = [ john ];
john = null; // overwrite the reference
// john is stored inside the array, so it won't be garbage-collected
// we can get it as array[0]
Similar to that, if we use an object as the key in a regular Map, then while the Map exists, that object exists as well. It occupies memory and may not be garbage collected
let john = { name: "John" };
let map = new Map();
map.set(john, "...");
john = null; // overwrite the reference
// john is stored inside the map,
// we can get it by using map.keys()
WeakMap -- Now, if we use an object as the key in it, and there are no other references to that object – it will be removed from memory (and from the map) automatically.
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // overwrite the reference
// john is removed from memory!
WeakMap keys must be objects, not primitive values.
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "ok"); // works fine (object key)
// can't use a string as the key
weakMap.set("test", "Not ok"); // Error, because "test" is not an object
Why????
Let's see below example.
let user = { name: "User" };
let map = new Map();
map.set(user, "...");
user = null; // overwrite the reference
// 'user' is stored inside the map,
// We can get it by using map.keys()
If we use an object as the key in a regular Map, then while the
Map exists, that object exists as well. It occupies memory and may
not be garbage collected.
WeakMap is fundamentally different in this aspect. It doesn’t
prevent garbage-collection of key objects.
let user = { name: "User" };
let weakMap = new WeakMap();
weakMap.set(user, "...");
user = null; // overwrite the reference
// 'user' is removed from memory!
if we use an object as the key in it, and there are no other
references to that object – it will be removed from memory (and from
the map) automatically.
WeakMap does not support iteration and methods keys(), values(), entries(), so there’s no way to get all keys or values from it.
WeakMap has only the following methods:
weakMap.get(key)
weakMap.set(key, value)
weakMap.delete(key)
weakMap.has(key)
That is obvious as if an object has lost all other references (like 'user' in the code above), then it is to be garbage-collected automatically. But technically it’s not exactly specified when the cleanup happens.
The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a WeakMap is not known. The engine may have cleaned it up or not or did it partially. For that reason, methods that access all keys/values are not supported.
Note:- The main area of application for WeakMap is an additional data storage. Like caching an object until that object gets garbage collected.
WeapMap in javascript does not hold any keys or values, it just manipulates key value using a unique id and define a property to the key object.
because it define property to key object by method Object.definePropert(), key must not be primitive type.
and also because WeapMap does not contain actually key value pairs, we cannot get length property of weakmap.
and also manipulated value is assigned back to the key object, garbage collector easily can collect key if it in no use.
Sample code for implementation.
if(typeof WeapMap != undefined){
return;
}
(function(){
var WeapMap = function(){
this.__id = '__weakmap__';
}
weakmap.set = function(key,value){
var pVal = key[this.__id];
if(pVal && pVal[0] == key){
pVal[1]=value;
}else{
Object.defineProperty(key, this.__id, {value:[key,value]});
return this;
}
}
window.WeakMap = WeakMap;
})();
reference of implementation

Javascript array becomes an object structure

I'm experiencing an odd behavior (maybe it isn't odd at all but just me not understanding why) with an javascript array containing some objects.
Since I'm no javascript pro, there might very well be clear explanation as to why this is happening, I just don't know it.
I have javascript that is running in a document. It makes an array of objects similar to this:
var myArray = [{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...];
If I print out this array at the place it was created like JSON.stringify(myArray), I get what I was expecting:
[{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...]
However, if I try to access this array from a child document to this document (a document in a window opened by the first document) the array isn't an array any more.
So doing JSON.stringify(parent.opener.myArray) in the child document will result in the following:
{"0":{"Id":"guid1","Name":"name1"},"1":{"Id":"guid2","Name":"name2"},...}
And this was not what I was expecting - I was expecting to get the same as I did in teh parent document.
Can anyone explain to me why this is happening and how to fix it so that the array is still an array when addressed from a child window/document?
PS. the objects aren't added to the array as stated above, they are added like this:
function objTemp()
{
this.Id = '';
this.Name = '';
};
var myArray = [];
var obj = new ObjTemp();
obj.Id = 'guid1';
obj.Name = 'name1';
myArray[myArray.length] = obj;
If that makes any difference.
Any help would be much appreciated, both for fixing my problem but also for better understanding what is going on :)
The very last line might be causing the problem, have you tried replacing myArray[myArray.length] = obj; with myArray.push(obj);? Could be that, since you're creating a new index explicitly, the Array is turned into an object... though I'm just guessing here. Could you add the code used by the child document that retrieves myArray ?
Edit
Ignore the above, since it won't make any difference. Though, without wanting to boast, I was thinking along the right lines. My idea was that, by only using proprietary array methods, the interpreter would see that as clues as to the type of myArray. The thing is: myArray is an array, as far as the parent document is concerned, but since you're passing the Array from one document to another, here's what happens:
An array is an object, complete with it's own prototype and methods. By passing it to another document, you're passing the entire Array object (value and prototype) as one object to the child document. In passing the variable between documents, you're effectively creating a copy of the variable (the only time JavaScript copies the values of a var). Since an array is an object, all of its properties (and prototype methods/properties) are copied to a 'nameless' instance of the Object object. Something along the lines of var copy = new Object(toCopy.constructor(toCopy.valueOf())); is happening... the easiest way around this, IMO, is to stringency the array withing the parent context, because there, the interpreter knows it's an array:
//parent document
function getTheArray(){ return JSON.stringify(myArray);}
//child document:
myArray = JSON.parse(parent.getTheArray());
In this example, the var is stringified in the context that still treats myArray as a true JavaScript array, so the resulting string will be what you'd expect. In passing the JSON encoded string from one document to another, it will remain unchanged and therefore the JSON.parse() will give you an exact copy of the myArray variable.
Note that this is just another wild stab in the dark, but I have given it a bit more thought, now. If I'm wrong about this, feel free to correct me... I'm always happy to learn. If this turns out to be true, let me know, too, as this will undoubtedly prove a pitfall for me sooner or later
Check out the end of this article http://www.karmagination.com/blog/2009/07/29/javascript-kung-fu-object-array-and-literals/ for an example of this behavior and explanation.
Basically it comes down to Array being a native type and each frame having its own set of natives and variables.
From the article:
// in parent window
var a = [];
var b = {};
//inside the iframe
console.log(parent.window.a); // returns array
console.log(parent.window.b); // returns object
alert(parent.window.a instanceof Array); // false
alert(parent.window.b instanceof Object); // false
alert(parent.window.a.constructor === Array); // false
alert(parent.window.b.constructor === Object); // false
Your call to JSON.stringify actually executes the following check (from the json.js source), which seems to be failing to specify it as an Array:
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
//stringify

Memory assignment to arrays

I was wondering if any one knows how memory is handled with JS arrays if you have an array that starts with a high value.
For example, if you have:
array[5000] = 1;
As the first value in the array, everything before 5000 simply does not exist, will the amount of memory assigned to the array cater for the unassigned 4999 positions prior to it... or will it only assign memory to the value in the array for [5000] ?
I'm trying to cut down on the amount of memory used for my script so this led to me wondering about this question :)
When assigning a value to the 5000th key, not the whole array is populated:
var array = []; // Create array
array[5000] = 1;
'1' in array; // false: The key does not exists
Object.keys(array); // 5000 (it's the only key)
If you want to blow your new browser with arrays, populate a typed array:
var array = new ArrayBuffer(6e9); // 6 Gigs
Both can be verified easily in Chrome: Open the console and memory console (Shift+Esc), and paste the code. window.a=new Array(6e9); or window.a=[];window[6e9]=1; doesn't result in a significant memory increase,
while window.a=new ArrayBuffer(6e9); crashes the page.
PS. 6e9 === 6000000000
Javascript is really interpreted and run by the browser, so it depends on how the browser implements this behavior. In theory, once you do array[5000], you have an array of 5001 elements, all except the 5001st being undefined.
Though if I were the one implementing the logic for running such script, undefined would be the default value if not assigned to anything else, meaning I could probably get away with defining a map with 1 entry assigning key 5000 to value 1. Any accesses to any other value in the array would automatically return undefined, without having to do unnecessary work.
Here's a test of this here. As you can see, the alert is seen immediately.
JS arrays are actually not arrays as you know them from other programming languages like C, C++, etc. They are instead objects with a array like way of accessing them. This means that when you define array[5000] = 1; You actually define the 5000 property of the array object.
If you had used a string as the array key you would have been able to access the index as a property as well to demonstrate this behavior, but since variable names can't start with a number array.5000 would be invalid.
array['key'] = 1;
alert( array.key ); // Gives you 1
This means that arrays will probably be implemented much like objects, although each implementation is free to optimize, thus giving you the behavior you except from objects where you can define object.a and object.z without defining the whole alphabet.

Categories