If you log your object you get this:
{ '[object Object]': 1 }
Hence why the below logs 1, as node2 is being interpreted as [object Object]
console.log(hash[node2]); //This is evaluating hash['object Object'] (Again)
To get around this, there are a number of approaches, one would be to stringify your object using the JSON API, and use the return value as the key.
E.g.
hash[JSON.stringify(node1)] = 1;
Now what you have is:
{'{"num":1,"right":null,"left":null}': 1 }
And thus, accessing hash[node2] will now be undefined, as expected.
hash[node2] === undefined; //true
hash[JSON.stringify(node2)] === undefined; //true
You would probably want to create a little API around this. As a very rough example:
class Hash {
constructor () {
this.hashes = {};
}
get (key) {
return this.hashes[JSON.stringify(key)];
}
set (key, value) {
this.hashes[JSON.stringify(key)] = value;
}
}
const hashStore = new Hash();
hashStore.set({foo: 'bar'}, 1);
hashStore.set({foo: 'cheese'}, 2);
console.log(hashStore.get({foo: 'bar'})); // 1
console.log(hashStore.get({foo: 'cheese'})); //2
Alternatively, if you are only using objects as keys which are in 'your control', then as Jakub Keller points out in his answer, you could override the toString function of your Node class.
The fallback of both approaches here is 'uniqueness', for either method to be a stable approach, you would want to introduce a unique key for each object, if you go with Jakub Keller's approach, you'd use that unique key in the toString override.
Both approaches satisfy a set of needs, if you're storing literals, an array of different objects as your keys, i'd probably go with my approach, and have the API write a custom, unique ID onto each object stored in the get method, again not perfect, as you could overwrite an existing key.