3

Let's say you want to compare two javascript objects or arrays for reference equality.

The most obvious solution would be

if (a === b) { /*...*/ };

The problem with this is that a and b would have to hold the references to actual objects. This can be a problem in same cases. For example, trying to hold actual references in a memoization function would create a memory leak.

One approach is to use JSON.stringify(a) to obtain string representations of objects and compare that. But that can be prohibitively expensive in some use cases. Not to mention that actual reference equality isn't taken into consideration.

That got me wondering. Is there a way to stringify an actual reference instead of the object contents? Obviously, you can't manipulate pointers in javascript, but what if you could get just a pointer representation of some kind. A hash sum? Raw memory location? Guid or integer representation?

I know there are some kinds of object id-s when analyzing memory dump in chrome dev tools. Maybe these things can be accessed during runtime? Using some kind of special switch in node.js?

Can anyone think of a way to get this done in javascript?

panta82
  • 2,763
  • 3
  • 20
  • 38
  • There is *no* provision in ECMAScript for this "exposing of references"; the specification doesn't even define "references" as such. There might be something node-specific. – user2864740 Sep 08 '14 at 11:04
  • I figured, this question is more in a "clever hack" territory. – panta82 Sep 08 '14 at 11:05
  • There are some object[-key] hash implementations floating about, but they have the same "problem" as they keep the original objects alive. However, without the object being alive there is no point talking about any opaque identifier for references. If wanting to get an actual "hash" value (either a simple 32-bit hash code or a SHA-x hash), that has to be done the "expensive" way by some algorithm that crawls over the relevant data (not necessarily using JSON); it could also be simply a required method on the objects that will participate in this system. – user2864740 Sep 08 '14 at 11:08
  • 1
    What is your usecase ? – Mudassir Ali Sep 08 '14 at 11:29
  • Memoization, for example. If you have huge objects you don't want to traverse. – panta82 Sep 08 '14 at 11:34

2 Answers2

3

If you want to use this for memorization (caching), you should use the ES6 WeakMap. It works from Firefox 6, Chrome 36 and IE 11 on.

The WeakMap object is a collection of key/value pairs in which the keys are objects and the values can be arbitrary values. [...] 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 four 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.

It works like a map in Java - you can use arbitrary objects as keys and as values. But the keys won't prevent garbage collection, so you don't have to worry about memory leaks, but still can find the memorized calculation for a given object.

Yogu
  • 9,165
  • 5
  • 37
  • 58
  • Interesting. Sounds custom-made for this problem. Unfortunately, poor browser support so far. Maybe it will be a realistic choice in a year or two. +1 – panta82 Sep 08 '14 at 11:43
  • Ok, not the perfect answer, but I'm accepting it as there doesn't seem to be a better way. – panta82 Sep 10 '14 at 07:30
0

Inspired by @user2864740's remark (dude, set up a name, seriously :-)).

it could also be simply a required method on the objects that will participate in this system

This implementation extends Object prototype to include a utility to generate unique UUID for each object. generateId implementation is taken from here.

Seems to be working for objects and arrays.

Object.prototype._id = function () {
    return this.__id || (this.__id = generateId());

    function generateId() {
        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
            var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
            return v.toString(16);
        });
    }
}

function assert(message, test) {
    if (!test) {
        throw new Error("ASSERTION FAILED: " + message);
    } else {
        console.log("PASSED: " + message);
    }
}


var x = { name: "same" },
    y = { name: "same" },
    z = [],
    x2 = x;

assert("Id is unchanging", x._id() === x._id());
assert("Id is different  for different objects", x._id() !== y._id());
assert("Arrays have id too", z._id() === z._id());
assert("Id is same for same objects", x._id() === x2._id());

Live example

I'm leaving this here to see if there are problems or if maybe someone has a better idea.

Community
  • 1
  • 1
panta82
  • 2,763
  • 3
  • 20
  • 38