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.
Related
I am using Map
because I want to store an object as a key.
My question is - can I access a map the same way I would access a plain object?
For example:
let m = new Map();
let obj = {foo:'bar'};
m[obj] = 'baz';
console.log(m[obj]);
is this supposed to work correctly as is, or do I need to use the get/set methods of a Map?
The reason I ask is because if I need to use get/set it forces to me to carefully refactor a lot of code.
Here is a real life example of code that may need to be refactored:
// before (broker.wsLock was plain object)
function addWsLockKey(broker, ws, key) {
let v;
if (!( v = broker.wsLock[ws])) {
v = broker.wsLock[ws] = [];
}
if (v.indexOf(key) < 0) {
v.push(key);
}
}
// after (broker.wsLock is a Map instance)
function addWsLockKey(broker, ws, key) {
let v;
if (!( v = broker.wsLock.get(ws))) {
v = [];
broker.wsLock.set(ws, v);
}
if (v.indexOf(key) < 0) {
v.push(key);
}
}
is there some way to set v on the same line as the set() call?
If you want access to the actual values of the Map object, then you have to use .get() and .set() or various iterators.
var m = new Map();
m.set("test", "foo");
console.log(m.get("test")); // "foo"
Regular property access on a Map such as:
m["test"] = "foo"
just sets a regular property on the object - it does not affect the actual map data structure.
I imagine it was done this way so that you can access the Map object properties separately from the members of the Map data structure and the two shall not be confused with one another.
In addition, regular properties will only accept a string as the property name so you can't use a regular property to index an object. But, map objects have that capability when using .set() and .get().
You asked for a "definitive" answer to this. In the ES6 specification, you can look at what .set() does and see that it operates on the [[MapData]] internal slot which is certainly different than the properties of an object. And, likewise, there is no where in that spec where it says that using normal property access would access the internal object [[MapData]]. So, you'll have to just see that normal property access is describe for an Object. A Map is an Object and there's nothing in the Map specification that says that normal property access should act any different than it does for any other object. In fact, it has to act the same for all the methods on the Map object or they wouldn't work if you happened to put an item in the Map with the same key as a method name. So, you're proof consists of this:
A simple test will show you that property access does not put anything in the Map itself, only a regular property.
The spec describes a Map as an object.
The spec describes how .get() and .set() operate on the internal slot [[MapData]].
There's nothing in the spec that says property access on a Map object should work any different than it always does.
If property access did access the MapData, then you would not be able to access methods if you happened to put a key in the Map that conflicted with a method name - that would be a mess if that was the case.
I have a custom class called Foo:
class Foo {
constructor(public v: any) {
}
}
And I have a map where Foo is my Key:
const map = new Map<Foo, string>();
AFAIK TypeScript doesn't have comparision overloading. How can I make sure that getting the key works properly?
const foo = new Foo(1234);
map.get(foo);
Here is the entire code:
class Foo {
constructor(public v: any) {
}
}
const map = new Map<Foo, string>();
const foo = new Foo(1234);
map.set(foo, "HELLO WORLD");
const foo2 = new Foo(1234);
console.log(map.get(foo2)); // DOESN'T work
console.log(map.get(foo)); // DOES work of course
You can find my problem here: https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8BQ0PWPAdhALgE4Cuw+8hAFAA7EBGIAlsNAG4Bc0YOAngJQpU6TAF9UYodjz5oAWzDVoAXmg4ApgHdoAWQUAeBPAA00AoUY4A5gD5KfANxDUUgtABmiZas1xElAIwATADMACwOqPLUAHQQaviUHsbQAEQAEgCiADJZAPLQAOq5AEpZACIpEZK4rkmBXupahgEh4Y4u8CBq0SDwlpRR0ZbxiYiBfA7QAPRT0AAm8GoQOADkMhoUANZOHV09fQMKQyNJE-bTs2W5GQDK0BuE26hAA
The problem you have isn't related to TypeScript, but rather JavaScript not having referential transparency. Therefore comparison like this new Foo(123) === new Foo(123) is always falsy.
Solution
The JavaScript's Map has a rather limited api. If you want to find specific entry using custom comparison function (in this case comparing by value), I'd suggest using .entries() method to get all the entries from the Map in form of an array of tuples [key, value][]. You can then find there whatever entry you want using Array.find(). The Map itself doesn't have any such method.
However, even better approach would be to rethink your data structure. Considering the limitations of the Map it would be better to have a primitive data type as a key in the Map. Since I don't know the specifics of your case I can only offer this general suggestion.
If I wanted to clone any javascript object (that's not null), I would think I could just copy all of its own properties (enumerable and non-enumerable) -- using Object.getOwnPropertyNames -- onto a new empty object.
But I've noticed that an example of a deep cloning function provided by Dojo toolkit (https://davidwalsh.name/javascript-clone) treats RegExp, Date, and Node objects as special cases, and lodash.cloneDeep also has a lot of logic that is a lot more complicated than simply copying properties, including having some special cases of its own and apparently not supporting all types of objects: (https://github.com/lodash/lodash/blob/master/.internal/baseClone.js).
Why is simply copying the object properties not sufficient? What else is there to a javascript object besides its properties that I don't know about?
EDIT: to be clear, I'm talking about deep cloning an object. Sorry for the confusion.
If the top level properties are all value objects like strings and numbers then just copying the top level properties is fine for a clone of an object. If there are any reference objects such as dates, arrays or other objects then all your are doing is copying a reference from one object to another. If you change the reference object on the clone you will mutate the original object.
Take a look at my clone function at https://stackblitz.com/edit/typescript-qmzgf7
If it is an array it clones every item in the array, if it is a date it creates a new date with the same time, if it is an object it clones every property else if just copies the property.
The cloned object can now be mutated without worrying about effects it might have on the original object.
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: (typeof obj === 'object') && obj
? Object.getOwnPropertyNames(obj).reduce((o, prop) => ({ ...o, [prop]: clone(obj[prop]) }), {})
: obj;
let original = { prop1: "Original", objProp: { prop1: "Original" } };
let swallowCopy = { ...original };
let clonedObj = clone(original);
clonedObj.prop1 = "Changed";
clonedObj.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
swallowCopy.prop1 = "Changed";
swallowCopy.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
Notice how modifying the property on the object property shallow copy causes the original to change as well.
The easiest way to clone an object in JS is by using the ... spread operator.
Let's say you have this object:
const object = { foo: 1, bar: 2 }
To clone it, you can simply declare:
const objectClone = {...object}.
This will create all the properties present in the original object onto the clone, as well as their values.
Now the problem is, if you have any object nested in there, the copies will be made by reference. Suppose the original object is this instead:
const student = { studentID: 1, tests: { test1: 90, test2: 95}}
If you create a copy of that object by using the spread operator(or Object.assign, spread is just syntactic sugar), the nested object will actually point to the object inside the original object! So repeating this:
const studentClone = {...student}
And now you edit a property of the nested object inside the clone:
studentClone.tests.test1 = 80
This will change the value in both clone, and original object, as the nested object is really just pointing to 1 object in memory.
Now what those utilities, like _.cloneDeep will do, is iterate through all inner objects in the object you're cloning, and repeat the process. You could technically do it yourself, but you wouldn't be able to do it on objects with many nested objects easily. Something like this:
const studentClone = {...studentClone, tests: {...studentClone.tests}}
This would create new objects, with no reference problems.
Hope this helped!
EDIT: Just adding, object spreading would only work properly for prototype objects, of course. Each instantiated objects,such as arrays, Date objects etc, would have their own way of cloning.
Arrays can be copied similarly, through [...array]. It does follow the same rules regarding to references. For dates, you can simply pass the original date object into the Date constructor again:
const clonedDate = new Date(date)
This is where the third-party utilities will come in handy, as they'll usually handle most use cases.
This answer does a good job of explaining two of the problems with cloning a normal JavaScript object: prototype properties and circular references. But to answer your question regarding certain built-in types, the TL;DR answer is that there are 'under the hood' properties that you have no programmatic access to.
Consider:
let foo = [1, 2];
let bar = {};
Object.assign(bar, foo);
Object.setPrototypeOf(bar, foo.constructor.prototype); // aka Array.prototype
bar[0]; // 1
bar instanceof Array; // true
bar.map(x => x + 1); // [] ????
Empty array? Why? Just to make sure we're not crazy
foo.map(x => x + 1); // [2, 3]
The reason why map (and the other array methods) fail to work is that an Array isn't simply an object: it has internal slot properties for the stuff you put in it that you don't get to see as the JavaScript programmer. As another example, every JavaScript object has an internal [[Class]] property that says what kind of object it is. Fortunately for us, there's a loophole in the spec that allows us indirect access to it: the good ol Object.prototype.toString.call hack. So let's see what that has to say about various stuff:
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(3); // [object Number]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(/\w/); // [object RegExp]
Object.prototype.toString.call(JSON); // [object JSON]
Object.prototype.toString.call(Math); // [object Math]
Let's see what it says about our foo and bar:
Object.prototype.toString.call(foo); // [object Array]
Object.prototype.toString.call(bar); // [object Object] Doh!
There's no way to 'convert' a random object to an Array... or a Date... or an HTMLElement... or a regex. Now, there are in fact ways to clone all of those things, but they require special logic: you can't just copy properties, or even set the prototype, because they have internal logic you can't access or directly replicate.
In normal everyday JavaScript programming we don't worry too much about this stuff, it's the kind of thing that's generally of interest to library authors (or language implementers). We everyday working stiffs just use a library to cover the edge cases and call it a day. But every once in a while the abstractions we use leak and the ugly bubbles through. This is however a great illustration of why you should probably use battle-tested libraries rather than trying to roll your own.
An object in javascript includes fields and functions together, and every field could be another object (Like Date type). If you copy a date field, it will be a reference type assignment.
Example:
var obj1 = { myField : new Date('2018/9/17') };
var obj2 = {};
obj2.myField = obj1.myField;
Now, if we change "obj2.myField" like this:
obj2.myField.setDate(obj2.myField.getDate() + 2);
console.log(obj1.myField); // Result =====> Wed Sep 19 2018 00:00:00 GMT+0430
As you see, obj1 and obj2 still are linked.
Correct way to copy a date field:
obj2.myField = new Date(obj1.myField.getTime());
Most native objects(like you have mentioned - I don't know for is the correct naming for them; maybe built-in?) are treated as "simple": it does not make sense to copy Date object property-by-property. In the same time they all are mutable in some way.
let a = {test: new Date(1)}; // test === Thu Jan 01 1970 00:00:00GMT
let copy_a = {test: a.test}; // looks like cloned
a.test.setDate(12); // let's mutate original date
console.log(copy_a.test); // Thu Jan 12 1970 00:00:00GMT ooops modified as well
So you either should handle that exceptions(special cases) explicitly or take a risk of side effects for some cases.
UPDATE: Recently a brilliant article from Mozilla came up. Read it if you're curious.
As you may know they are planning to include new Symbol primitive type in ECMAScript 6 (not to mention some other crazy stuff). I always thought that the :symbol notion in Ruby is needless; we could easily use plain strings instead, like we do in JavaScript. And now they decide to complicate things in JS with that.
I don't understand the motivation. Could someone explain to me whether we really need symbols in JavaScript?
The original motivation for introducing symbols to Javascript was to enable private properties.
Unfortunately, they ended up being severely downgraded. They are no longer private, since you can find them via reflection, for example, using Object.getOwnPropertySymbols or proxies.
They are now known as unique symbols and their only intended use is to avoid name clashes between properties. For example, ECMAScript itself can now introduce extension hooks via certain methods that you can put on objects (e.g. to define their iteration protocol) without risking them to clash with user names.
Whether that is strong enough a motivation to add symbols to the language is debatable.
Symbols do not guarantee true privacy but can be used to separate public and internal properties of objects. Let's take an example where we can use Symbol for having private properties.
Let's take an example where a property of an object is not private.
var Pet = (function() {
function Pet(type) {
this.type = type;
}
Pet.prototype.getType = function() {
return this.type;
}
return Pet;
}());
var a = new Pet('dog');
console.log(a.getType());//Output: dog
a.type = null;
//Modified outside
console.log(a.getType());//Output: null
Above, the Pet class property type is not private. To make it private we have to create a closure. The below example illustrates how we can make type private using a closure.
var Pet = (function() {
function Pet(type) {
this.getType = function(){
return type;
};
}
return Pet;
}());
var b = new Pet('dog');
console.log(b.getType());//dog
b.type = null;
//Stays private
console.log(b.getType());//dog
Disadvantage of above approach: We are introducing an extra closure for each Pet instance created, which can harm performance.
Now we introduce Symbol. This can help us make a property private without using extra unnecessary closures. Code example below:
var Pet = (function() {
var typeSymbol = Symbol('type');
function Pet(type) {
this[typeSymbol] = type;
}
Pet.prototype.getType = function(){
return this[typeSymbol];
}
return Pet;
}());
var a = new Pet('dog');
console.log(a.getType());//Output: dog
a.type = null;
//Stays private
console.log(a.getType());//Output: dog
This post is about the Symbol(), supplied with actual examples I could find/make and facts & definitions I could find.
TLDR;
The Symbol() is the data type, introduced with the release of ECMAScript 6 (ES6).
There're two curious facts about the Symbol.
the first data type and only data type in JavaScript which has got no literal
any variable, defined with Symbol(), gets unique content, but it's not really private.
any data has its own Symbol, and for the same data the Symbols would be the same. More info in the following paragraph, otherwise it's not a TLRD; :)
How do I initialise the symbol?
1. To get a unique identifier with a debuggable value
You can do it either this way:
var mySymbol1 = Symbol();
Or this way:
var mySymbol2 = Symbol("some text here");
The "some text here" string can't be extracted from the symbol, it's just a description for debugging purposes. It doesn't change the behaviour of symbol in any way. Although, you could console.log it (which is fair, since the value is for debugging, so as not to mistake that log with some other log entry):
console.log(mySymbol2);
// Symbol(some text here)
2. To obtain a symbol for some string data
In this case the value of symbol is actually taken into account and this way two symbols may be non-unique.
var a1 = Symbol.for("test");
var a2 = Symbol.for("test");
console.log(a1 == a2); //true!
Let's call those symbols "second-type" symbols. They do not intersect with the "first-type" symbols (i.e. the ones defined with Symbol(data)) in any way.
The next two paragraphs pertain only the first-type symbol.
How do I benefit from using Symbol instead of the older data types?
Let's first consider an object, a standard data type. We could define some key-values pairs there and have an access to the values by specifying the key.
var persons = {"peter":"pan","jon":"doe"};
console.log(persons.peter);
// pan
What if we have two persons with the name Peter?
Doing this:
var persons = {"peter":"first", "peter":"pan"};
wouldn't make much sense.
So, appears to be a problem of two absolutely different persons having a same name. Let's then refer out new Symbol(). It's like a person in real life - any person is unique, but their names can be equal. Let's define two "persons".
var a = Symbol("peter");
var b = Symbol("peter");
Now we have got two different persons with the same name. Are our persons different indeed? They are; you can check this:
console.log(a == b);
// false
How do we benefit there?
We can make two entries in your object for the different persons and they can't be mistaken in any way.
var firstPerson = Symbol("peter");
var secondPerson = Symbol("peter");
var persons = {[firstPerson]:"first", [secondPerson]:"pan"};
Note:
It's worth to notice, though, that stringifying the object with JSON.stringify will drop all the pairs initialised with a Symbol as a key.
Executing Object.keys won't either return such Symbol()->value pairs.
Using this initialisation, it's absolutely impossible to mistake the entries for the first and second persons. Calling console.log for them will correctly output their second names.
console.log(persons[a]);
// first
console.log(persons[b]);
// pan
When used in object, how it is different compared to defining non-enumerable property?
Indeed, there already existed a way to define a property to be hidden from Object.keys and enumeration. Here it is:
var anObject = {};
var fruit = "apple";
Object.defineProperty( anObject, fruit, {
enumerable: false,
value: "green"
});
What difference does Symbol() bring there? The difference is that you can still get the property defined with Object.defineProperty in the usual way:
console.log(anObject[fruit]); //green
console.log(anObject["apple"]); //green
console.log(anObject.apple); //green
And if defined with Symbol as in previous paragraph:
fruit = Symbol("apple");
You will have an ability to receive its value only if knowing its variable, i.e.
console.log(anObject[fruit]); //green
console.log(anObject["apple"]); //undefined
console.log(anObject.apple); //undefined
Moreover, defining another property under the key "apple" will make the object drop the older one (and if hard-coded, it could throw an error). So, no more apples! That's a pity. Referring the previous paragraph, the Symbols are unique and defining a key as Symbol() will make it unique.
Type conversion and checking
Unlike other data types, it's impossible to convert the Symbol() to any other data type.
It's possible to "make" a symbol based on primitive data type by calling Symbol(data).
In terms of checking the type, nothing changes.
function isSymbol ( variable ) {
return typeof someSymbol === "symbol";
}
var a_Symbol = Symbol("hey!");
var totally_Not_A_Symbol = "hey";
console.log(isSymbol(a_Symbol)); //true
console.log(isSymbol(totally_Not_A_Symbol)); //false
Symbols are a new, special kind of object that can be used as a unique property name in objects. Using symbols instead of a strings allows different modules to create properties that don’t conflict with one another. Symbols can also be made effectively private, so that their properties can’t be accessed by anyone who doesn’t already have direct access to the symbol.
Symbols are a new primitive, just like the number, string, and boolean primitives. Unlike the other primitives, symbols do not have a literal syntax (e.g. how string has '') — the only way to create them is with the Symbol constructor in the following way:
let symbol = Symbol();
In reality, symbols are just a slightly different way to attach properties to an object — you could easily provide the well-known symbols as standard methods, just like Object.prototype.hasOwnProperty, which appears in everything that inherits from Object.
Here are some of the benefits of the Symbol primitive type.
Symbols have debuggability built in
Symbols can be given a description, which is really just used for debugging to make life a little easier when logging them to a console.
Symbols can be used as object keys
This is where symbols get really interesting. They are heavily intertwined with objects. Symbols can be assigned as keys to objects, meaning you can assign an unlimited number of unique symbols to an object and be guaranteed that these will never conflict with string keys, or other unique symbols.
Symbols can be used as unique values
Let’s assume you have a logging library, which includes multiple log levels such as logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARN and so on. In ES5 code you’d like make these strings (so logger.levels.DEBUG === 'debug'), or numbers (logger.levels.DEBUG === 10). Both of these aren’t ideal as those values aren’t unique values, but symbols are! So logger.levels simply becomes:
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn'),
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');
Read more in this great article.
Here is how I see it. Symbols provide 'an extra level of privacy', by preventing the keys/properties of an object from being exposed through some popular methods such as Object.keys() and JSON.stringify().
var age = Symbol(); // declared in another module perhaps?
class Person {
constructor(n,a){
this.name = n;
this[age] = a;
}
introduce(){
console.log(`My name is ${this.name}. I am ${this[age]-10}.`);
}
}
var j = new Person('Jane',45);
j.introduce(); // My name is Jane. I am 35.
console.log(JSON.stringify(j)); // {"name":"Jane"}
console.log(Object.keys(j)); // ["name"]
console.log(j[age]); // 45 (well…only if you know the age in the first place…)
Although given an object per se, such properties can still be exposed through reflection, proxy, Object.getOwnPropertySymbols() etc., there is no natural means to access them through a few direct methods, which may be sufficient sometimes from an OOP perspective.
A JS symbol is a new primitive data type. They are tokens that serve as unique IDs. A symbol can be created using the Symbol constructor. Take for instance this snippet from MDN:
// The symbol constructor takes one optional argument,
// the descriptions which is used for debugging only.
// Here are two symbols with the same description
let Sym1 = Symbol("Sym");
let Sym2 = Symbol("Sym");
console.log(Sym1 == Sym2); // returns "false"
// Symbols are guaranteed to be unique.
// Even if we create many symbols with the same description,
// they are different values.
It is often handy to use symbols as unique object property keys, for example:
let obj = {};
let prop = Symbol();
obj[prop] = 123; // the symbol prop is assigned 123
obj.prop = 456; // the string prop is assigned 456
console.log(obj.prop, obj[prop]); // logs 456, 123
Symbols have two main use cases:
“Hidden” object properties. If we want to add a property into an object that “belongs” to another script or a library, we can create a
symbol and use it as a property key. A symbolic property does not
appear in for..in, so it won’t be accidentally processed together
with other properties. Also it won’t be accessed directly, because
another script does not have our symbol. So the property will be
protected from accidental use or overwrite.
So we can “covertly” hide something into objects that we need, but
others should not see, using symbolic properties.
There are many system symbols used by JavaScript which are accessible as Symbol.*. We can use them to alter some built-in
behaviors. For instance, ......
Symbol.iterator for iterables, Symbol.toPrimitive to setup
object-to-primitive conversion and so on.
Source
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