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
Related
I want to produce a hash derived from an Object's reference, and then associate the hash string to the object itself into a dictionary.
I have heard of object-hash and attempted the following implementation:
const objectHash = require('object-hash');
class A {
a: string;
constructor() {
this.a = '1';
}
}
let a = new A();
let ab = new A();
console.log(objectHash(a)); // 712041d72943c4794bb23d1d455e17b3a4ea17f5
console.log(objectHash(ab));// 712041d72943c4794bb23d1d455e17b3a4ea17f5
In despite of a and ab being different objects (although instances of the same class), the hash produced is equal, which is not the expected result. That is because the library hashes object values rather than object reference.
Expected result
let foo = new A();
let bar = new A();
const fooH = objectHash(foo); <-- Produces a unique hash string representing the reference of "foo"
const barH = objectHash(bar); <-- Produces a unique hash string representing the reference of "bar"
let objects = {
fooH: foo,
barH: bar
}
This could probably be achieved in C++ or C# by using pointers and addresses, but what about Javascript?
If anybody can guide me on the right path to incapsulate an object's reference into a string, it would be appreciated.
It is not possible to get an object's reference in JavaScript in order to calculate a hash of it. The reference is an internal value for the engine that is not normally exposed.
However, if you want to index some data against an object, you can use a Map, since the keys can be any arbitrary JavaScript value, thus allowing objects. Since objects are unique during comparison, that means that the same object returns the value:
class A {
constructor() {
this.a = '1';
}
}
const foo = new A();
const bar = new A();
const map = new Map()
.set(foo, "hello")
.set(bar, "world");
console.log(map.get(foo), map.get(bar));
JavaScript also has a WeakMap - it only allows objects as keys but in turn it only holds a weak reference to them. This means that once any other reference to the key is gone, the WeakMap will not prevent the object from being garbage collected.
Maps, and especially WeakMaps, are a viable way to extend foreign objects without actually having to nest them in your own hierarchy. Here is a quick demo
import {a, b} from 'foreign';
const extension = new WeakMap()
extension.set(a, {
myProperty1: true,
myProperty2: 40,
myProperty3: "hello",
});
extension.set(b, {
myProperty1: false,
myProperty2: 2,
myProperty3: "world"
});
Here we get some foreign objects a, and b we do not control and extend them with our own properties without ever needing to change or clone them. Looking the object in the WeakMap gives the extended properties.
See a more detailed description by Benjamin Gruenbaum
For further reference a Set and a WeakSet exhibit the same properties as a Map and WeakMap, respectively, in terms of storing objects. However, they without an extra value for the object. That can be useful, for example, to keep track of seen objects. Which event handlers have fired, or which nodes in a graph have been visited, and similar.
I am having trouble maintaining the original value of a variable after making new changes to the original variable.
Code:
(...)
data = Illumination.calculate_N(data)
data = Illumination.calculate_pi(data)
data = Illumination.calculate_kwh(data)
data = Illumination.calculate_ca(data)
let data_base = data
let ca_base = data.ca
let kwh_base = data.kwh
let pi_base = data.pi
(...)
data = Illumination.calculate_N(data)
data = Illumination.calculate_pi(data)
data = Illumination.calculate_kwh(data)
data = Illumination.calculate_ca(data)
let data_proposto = data
let ca_proposto = data.ca
let kwh_proposto = data.kwh
let pi_proposto = data.pi
-----------------------------------
EXAMPLE:
static calculate_ai(data){
data.ai = data.areaTotal*data.au
return data
}
It was expected that the original variable (date) would have its values changed, and this happens correctly, however, the variables data_base and data_proposto are not keeping their values
Both variables at the end of the calculation have the same values as the variable date
The variables ca_proposto, ca_base, and the like store their values correctly
Any idea?
The only interactions of the variables data_base and data_proposto were their creations with the data variable and their return of the function
OBS: If I use console.log () to view the value of the data_base variable before redoing the new calculations (Illumination.calculate_N (data)), the value of the variable appears correctly as it should, it is changed shortly after these calculations.
Because in both cases you are assigning not the object itself in the current state, but a reference to that object. What you need to do is to clone the object so the state is frozen at that point.
Simple Clone (Shallow Copy)
let data_base = Object.assign({}, data); //you get a clone of data
let data_proposto = Object.assign({}, data);
The limitation here is that it only does a shallow copy. See Deep Copy below for further explanation.
JSON Clone
This is a quick-and-dirty way to clone as it converts a JSON object to a string, and then back. i.e. you are no longer getting a reference, but a new object.
let data_base = JSON.parse(JSON.stringify(data));
let data_postero = JSON.parse(JSON.stringify(data));
But this won't work if your object is not JSON-safe.
Deep Copy
The least elegant method is probably safest. It deep copies the properties over into a new object. The key difference with Object.assign() is that it copies the values of nested properties, whereas Object.assign() copies the reference to nested objects.
So with Object.assign() any subsequent changes in your nested objects will affect all versions of your "clones". This won't happen if your clones only have property values of those nested objects at the time of cloning – these values are not affected by any changes to the nested objects.
const deepCopy = function(src) {
let target = {};
// using for/in on object also returns prototype properties
for (let prop in src) {
// .hasOwnProperty() filters out these prototype properties.
if (src.hasOwnProperty(prop)) {
target[prop] = src[prop]; //iteratively copies over values, not references
}
}
return target;
}
let data_base = deepCopy(data);
let data_postero = deepCopy(data);
#chatnoir Defined the problem very well, But I do not agree with his JSON serialization solution due to the below probleam:
You will lose any Javascript property that has no equivalent type in
JSON, like Function or Infinity. Any property that’s assigned to
undefined will be ignored by JSON.stringify, causing them to be missed
on the cloned object.
My suggestion to perform deep copy is to rely on a library that’s well
tested, very popular and carefully maintained: Lodash.
Lodash offers the very convenient clone and deepclone functions to perform shallow and deep cloning.
Lodash has this nice feature: you can import single functions separately in your project to reduce a lot the size of the dependency.
Please find the running sample code here: https://glitch.com/edit/#!/flavio-lodash-clone-shallow-deep?path=server.js:1:0
You are using the same variable data inside and outside functions.
ie; data is in the global scope.
static calculate_ai(data){
data.ai = data.areaTotal*data.au
return data
}
even though you are expecting the scope of the variable data inside the method calculate_ai to be limited to that method, it is not the case. data is in global scope and therefore, the value changes inside the method for the variable affects outside as well.
An effective solution is to use a different variable inside the method.
A variable is like an octopus tentacle, and not as a box (as it’s commonly described). In this analogy, the variable's name can be thought of as the name of a tentacle.
A variable (tentacle) holds on to a value in what’s called a binding. A binding is an association of a variable to a value: x = 1.
In JavaScript, if a variable b holds on to variable a, changing the value to which variable a holds onto, will change the value to which variable b holds onto, as b and a are referencing to the same value:
let a = {key: 1}
let b = a
console.log(`a: ${a.key}`) // -> 1
console.log(`b: ${b.key}`) // -> 1
a.key = 2
console.log(`a: ${a.key}`) // -> 2
console.log(`b: ${b.key}`) // -> 2
a = {key: 3} // This will point variable 'a' to a new object, while variable 'b' still points to the original object.
console.log(`a: ${a.key}`) // -> 3
console.log(`b: ${b.key}`) // -> 2
I am trying to get into Fuse to create mobile apps and they use JavaScript for their logic. I never used JavaScript before and just recently completed their getting started course. Most of the stuff is pretty easy to understand, but I am having trouble with the way they use variables at one point. It would be nice, if somebody could explain how variables behave in JavaScript.
So the problem I have goes as follows:
for (var i = 0; i < hikes.length; i++){
// A new variable gets the value of the array
var hike = hikes[i];
if (hike.id == id){
// The variable gets a new value
hike.name = "foo";
break;
}
}
So, in my understanding of programming, the array hikes should be unchanged and only the variable hike should have foo as the name value. But in reality, the array now also has the name foo.
I guess the variable works as a pointer to the address of the arrays value, but maybe somebody can help me to better understand that concept.
Yes you're right, objects and arrays are always passed as references:
a = {}; // empty object
b = a; // references same object
b.foo = 'bar';
a.foo; // also 'bar'
You can create a deep copy of the array using JSON.parse(JSON.stringify(hikes)); and then use that copied array for manipulation:
var hikes = [
{
'id': 10
}
];
var id = 10;
var tempHikes = JSON.parse(JSON.stringify(hikes));
for (var i = 0; i < tempHikes.length; i++){
// A new variable gets the value of the array
var hike = tempHikes[i];
if (hike.id == id){
// The variable gets a new value
hike.name = "foo";
console.log('hike is ', hike);
break;
}
}
console.log(hikes);
arrays in javascript are passed by reference, whenever you modify an element in an array that change will occur anywhere you are accessing that array, to avoid such issues you have to use Array.from(arg) which creates a new array of from the arg parameter. This also applies to objects, to avoid such issues with objects, you have to use Object.create(obj) to create a new obj of from obj parameter or you can use let newObj = Object.assign( {} , obj ) , whenever you make any modification to the members of newObj the obj object does not see it, in other words there is no direct linkage between this two object, same thing applies for array
Boolean, null, undefined, String, and Number values are called primitive types.
When you assign something that is not a primitive type, namely arrays, functions and objects you are storing a reference to that.
That means that hikes[i] contains a reference to the object, where reference roughly means a pointer to it's location in memory.
When you assign hike = hikes[i] you are copying over the reference and not the actual object. So in fact hike still points to the same object as hikes[i], so any changes to that object are visible on both occasions.
If you want to copy the underlying object, there are different ways of doing so. One of them is Object.assign:
var hike = Object.assign({}, hikes[i])
This is because of pass by reference. All you need to do is create a new object (string, number ...) that you can work on.
for (var i = 0; i < hikes.length; i++){
var hike = hikes.slice(i,i+1)[0];
if (hike.id == id){
hike.name = "foo";
break;
}
}
slice also create a deep copy. you can use splice or assign or ((key1, key2)=>(key1, key2))(obj) etc.
A JavaScript WeakMap does not allow you to get the key, or the length or size, by design.
Is it possible to nevertheless loop over entries in some way ?
If not .. how does the Chrome console do this ?
Is it possible to nevertheless loop over entries in some way?
No, as you say, the contents of a WeakMap are not accessible by design, and there is no iterability.
If not … how does the Chrome console do this?
The console uses the debugging API of the JS engine, which allows access to the internals of objects (also to promise states, wrapped primitives, etc.) and many more.
Things are moving and it will soon be possible to create iterable week maps thanks to weak refs.
See the iterable WeakMap example in the tc39 weakrefs proposal.
(note that it is already possible with nodejs v12.?.? using --harmony-weak-refs flag)
Running far afield with a qualifier in your (2015) question, specifically:
Is it possible to nevertheless loop over entries in some way ?
Yes.
In one ridiculous situation, it is possible to emulate and then iterate the keys and values of a WeakMap, and also to make a proper, independent copy of your WeakMap.
If the WeakMap you wish to clone was built in a very specific fashion by a constructor function, you can do it:
// Define a Constructor-Function
// that makes objects
// containing WeakMaps:
function makeWeakMapObject(){
this.wm1 = new WeakMap();
this.o1 = {};
this.o2 = {"orange":"orange"};
this.wm1.set(this.o1, 37);
this.wm1.set(this.o2, 'azerty');
}
// Construct a new object:
let constructedWeakMapObject = new makeWeakMapObject();
// Then set a new key-value pair
// on the WeakMap in your object;
// because, ya know, otherwise you'd
// just reuse the WeakMap constructor
// and wouldn't need to clone :D
constructedWeakMapObject.added = {"ya":"glad"};
constructedWeakMapObject.wm1.set(constructedWeakMapObject.added, 42);
// In preparation to clone your newly constructed object,
// get that newly constructed object's property descriptors:
let props = Object.getOwnPropertyDescriptors(constructedWeakMapObject);
// Have a gander at those props; just for fun:
console.log({"props":props});
// Attempt to clone the constructedWeakMapObject
// using its ownPropertyDescriptors
let weakClone = new cloneWeak(props);
// and then check out what you made:
console.log({"weakClone":weakClone});
// Verify that you've made an independent clone
// (even though this example is shallow)
// by altering the WeakMap in your weakClone:
weakClone.wm.delete(weakClone.o1);
// Make sure your clone was altered:
console.log(weakClone.wm.get(weakClone.o1));
// And then check to see that the
// changes to your clone
// don't appear on your constructed object:
console.log(constructedWeakMapObject);
console.log(constructedWeakMapObject.wm1.get(constructedWeakMapObject.o1));
// A support function to help you use fresh keys in your cloned WeakMap to keep it independent from your original WeakMap
function cloneObject(obj) { // use something more robust, like underscore: _.cloneDeep(obj); actually, you'll likely have to roll your own so you can make clones of functions... anywho
var clone = {};
for(var i in obj) {
if(typeof(obj[i])==="object" && obj[i] !== null)
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
// Called as a constructor function w/arguments
function cloneWeak(inco){ // a bit wonky, at least in the middle
this.wm = new WeakMap();
let tempMap;
for(key in inco){
// Build keys on 'this' that match the incoming keys
if(Object.prototype.toString.call(inco[key].value) !== "[object WeakMap]"){
this[key] = cloneObject(inco[key].value);
}
// Reference the incoming map from your temp map
// (this makes the following loop possible)
else{tempMap = inco[key].value;}
}
this.fakeForHack = {}; // no idea why this works
this.wm.set(this.fakeForHack, "ok"); // no idea why this works... without it, the WeakMap entry for made.wm1.get(made.added) won't transfer -> ???
for(key in inco){
if(Object.prototype.toString.call(inco[key].value) !== "[object WeakMap]"){
// Set values for 'this' WeakMap:
this.wm.set(this[key], tempMap.get(inco[key].value));
}
}
}
It's kinda ugly, it's brittle, and it only solves a ridiculous edge-case; you're welcome!
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.