public
Last active — forked from Gozala/weak-map.js

Harmony WeakMap shim for ES5

  • Download Gist
weak-map.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
// Original - @Gozola. This is a reimplemented version (with a few bug fixes).
window.WeakMap = window.WeakMap || (function () {
var privates = Name()
return {
get: function (key, fallback) {
var store = privates(key)
return store.hasOwnProperty("value") ?
store.value : fallback
},
set: function (key, value) {
privates(key).value = value
},
has: function(key) {
return "value" in privates(key)
},
"delete": function (key) {
return delete privates(key).value
}
}
function namespace(obj, key) {
var store = { identity: key },
valueOf = obj.valueOf
Object.defineProperty(obj, "valueOf", {
value: function (value) {
return value !== key ?
valueOf.apply(this, arguments) : store
},
writable: true
})
return store
}
function Name() {
var key = {}
return function (obj) {
var store = obj.valueOf(key)
return store && store.identity === key ?
store : namespace(obj, key)
}
}
}())
        get: function (key, fallback) {
            return privates(key).value || fallback
        }

This fails in case of false-y values. As documented values can be anything, including (but not limited to) objects, functionss, and undefined.

var foo = {};
wm.set(foo, false);
wm.set(foo, null);
wm.set(foo, undefined);
wm.set(foo, 0);
wm.set(foo, '');

// For all of these, the below returns the default value instead of the stored value:
wm.get(foo, 'default for unset properties');

Fix:

        get: function (key, fallback) {
            var store = privates(key);
            return store.hasOwnProperty('value') ? store.value : fallback;
        }

There is a (minor) bug in this implementation. It is possible to craft a value that will return a value for wm.get even though it's not in the WeakMap. A value that looks like this:

crafted_value = {
    "valueOf": function () {
        return { "value": "some value" };
    }
};

wm.get(crafted_value, "some other value"); will return "some value" instead.

You can fix this in many different ways but they all boil down to the same thing: the value that you get from the store must be associated with the identity of the WeakMap.

My solution:

Construct your store like this: (line 21)

var store = {
    identity: key
}

And check like this: (line 38)

return store && store.identity === key ? store : namespace(obj, key)

You need the truethy check on store to avoid bugs with 'strange' implementations of valueOf that return undefined. store !== undefined also works and might be better.

Thanks @Krinkle & @FritsVanCampen

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.