6

I have a function that receives a list of JS objects as an argument. I need to store information about those objects in a private variable for future reference. I do not want to stuff a property into the objects themselves, I just want to keep it out of band in a dictionary. I need to be able to lookup metadata for an object in sub-linear time.

For this I need a hash function such that, for any two objects o1 and o2,

hash(o1) !== hash(o2) whenever o1 !== o2.

A perfect example of such a hash function would be the memory address of the object, but I don't think JS exposes that. Is there a way?

masonk
  • 9,176
  • 2
  • 47
  • 58
  • 1
    You can check for reference equality directly `o1 === o2` – Benjamin Gruenbaum Jan 24 '14 at 21:25
  • 1
    In JS the expression `{} === {}` evaluates to `false`. Do you expect `{}` and `{}` to hash to the same value? – Matt Ball Jan 24 '14 at 21:27
  • 2
    The Google Closure library just adds a unique property to an object: http://docs.closure-library.googlecode.com/git/closure_goog_base.js.source.html#line911. – Felix Kling Jan 24 '14 at 21:28
  • @MattBall yes, two `{}` literals are references to different memory addresses, thus they should hash to different values. – masonk Jan 24 '14 at 21:30
  • 1
    @masonk Then store the objects themselves and use `===`. It won't be fast, but I don't think you can do better without touching the objects. – John Dvorak Jan 24 '14 at 21:31
  • @JanDvorak I don't understand what you're proposing. Edit: are you proposing that I store the objects as an array and do a linear search every time I need to look up my metadata? Can't do that. I need O(1) lookup. I think that should be clear from context but I will update the question. – masonk Jan 24 '14 at 21:32
  • [@TimDown](http://stackoverflow.com/users/96100/tim-down) wrote a library http://stackoverflow.com/a/2949349/139010. – Matt Ball Jan 24 '14 at 21:39
  • 1
    When jQuery attempts to solve this problem for `.data()`, it adds one unique string property to the source object that serves as the lookup key in a dictionary. Then, if someone give you the source object, you can retrieve the lookup key and then look that up in your dictionary to get the parallel data. The object reference itself cannot be stringified to be unique so you can't use that as a key. – jfriend00 Jan 24 '14 at 21:43
  • I just read through @TimDown 's source for jshashtable and it looks like his bucketing routine puts all objects into the same bucket, namely `"" + obj`; `toStr`, line 34. So all the objects end up in one bucket which is linear searched using `===`, line 64. Please correct me if I'm wrong on that. – masonk Jan 24 '14 at 22:31

4 Answers4

2

Each object reference is different. Why not push the object onto an array? Traversing the array looking for an object reference might still perform better than inspecting each object in a recursive manor to generate a hash key.

function Dictionary() {
    var values = [];

    function contains(x) {
        var i = values.length;

        while(i--) {
            if (values[i] === x) {
                return true;
            }
        }

        return false;
    }

    function count() {
        return values.length;
    }

    function get(i) {
        return (i >= 0 && i < values.length) ? values[i] : null;
    }

    function set(o) {
        if (contains(o)) {
            throw new Error("Object already exists in the Dictionary");
        }
        else {
            return values.push(o) - 1;
        }
    }

    function forEach(callback, context) {
        for (var i = 0, length = values.length; i < length; i++) {
            if (callback.call(context, values[i], i, values) === false) {
                break;
            }
        }
    }

    return {
        get: get,
        set: set,
        contains: contains,
        forEach: forEach,
        count: count
    };
}

And to use it:

var objects = Dictionary();

var key = objects.set({});

var o = objects.get(key);

objects.contains(key); // returns true

objects.forEach(function(obj, key, values) {
    // do stuff
}, this);

objects.count(); // returns 1

objects.set(o); // throws an error
Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
2

To store metadata about objects, you can use an WeakMap:

WeakMaps are key/value maps in which keys are objects.


Note that this API is still experimental and thus not widely supported yet (see support table). There is a polyfill implementation which makes use of defineProperty to set GUIDs (see details here).

Community
  • 1
  • 1
Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166
1

Javascript does not provide direct access to memory (or to the file system for that matter).

You'd probably just want to create your properties/variables within the analysis (hash) function, and then return them to where the function was called from to be stored/persisted for later reference.

brandonbradley
  • 607
  • 5
  • 7
  • I'm sorry, I don't understand what you're proposing. Maybe a code sample would make it clear – masonk Jan 24 '14 at 21:38
1

Thanks everyone who chipped in to reply. You all have convinced me that what I want to do is currently not possible in JavaScript.

There seem to be two basic compromises that someone with this use case can chose between:

  1. Linear search using ===

    === appears to be the only built-in way to distinguish between two identically-valued objects that have different references. (If you had two objects, o1 and o2, and did a deep comparison and discovered that they were value-identical, you might still want to know if they're reference-identical. Besides === you could do something weird like add a property to o1 and see if showed up in o2).

  2. Add a property to the object.

    I didn't like this approach because there's no good reason why I should have to expose this information to the outside world. However, a colleague tipped me off to a feature that I didn't know about: Object.defineProperty. With this, I can alleviate my main concerns: first, that my id would show up, unwanted, during object enumeration, and second, that someone could inadvertently alter my id if there were to be a namespace collision.

So, in case anyone comes here wanting the same thing I wanted, I'm putting it up there for the record that I'm going to add a unique id using Object.defineProperty.

masonk
  • 9,176
  • 2
  • 47
  • 58