Reputation: 2020
Standard Function.prototype.bind
method creates a new function on each call.
I need to store somewhere and reuse bounded variants of that function.
It is not rocket science to implement function bindArg
which works for object arguments:
const fnStorage = new WeakMap();
function bindArg(fn, arg) {
if (!fnStorage.has(fn)) {
fnStorage.set(fn, new WeakMap());
}
if (!fnStorage.get(fn).has(arg)) {
fnStorage.get(fn).set(arg, fn.bind(null, arg));
}
return fnStorage.get(fn).get(arg);
}
this solution works fine, but only for object arguments. It could be done for scalar arguments by changing WeakMap
to Map
. But this is not good at all, because Map
will keep reference to bounded variant of function and prevents from garbage collecting it.
Is there any way to implement bindArg
function, which is pure immutable without memory leaking for any type of arguments?
Upvotes: 1
Views: 136
Reputation: 74234
You can use a Map
for primitive data types and a WeakMap
for objects:
function isPrimitive(value) {
return Object(value) !== value;
}
var primStore = new Map;
var objStore = new WeakMap;
function bindArg(fn, arg) {
var store = isPrimitive(arg) ? primStore : objStore;
if (!store.has(arg)) store.set(arg, new WeakMap);
if (!store.get(arg).has(fn)) store.get(arg).set(fn, fn.bind(null, arg));
return store.get(arg).get(fn);
}
Note that we return store.get(arg).get(fn)
instead of store.get(fn).get(arg)
. This flipping doesn't make any difference to the actual semantics of the bindArg
function but it's necessary when we want to distinguish between primitive data types and objects.
Edit: Alternatively, you could create a new WeakMap2
data structure which stores primitive values in a Map
and objects in a WeakMap
as follows:
var WeakMap2 = defclass({
constructor: function () {
this.map1 = new Map;
this.map2 = new WeakMap;
if (arguments.length > 0) {
for (var object in argument[0]) {
if (isPrimitive(object))
throw new TypeError("Iterator value " +
object + " is not an entry object");
else this.set(object[0], object[1]);
}
}
},
get: function (key) {
return (isPrimitive(key) ? this.map1 : this.map2).get(key);
},
set: function (key, value) {
return (isPrimitive(key) ? this.map1 : this.map2).set(key, value);
},
has: function (key) {
return (isPrimitive(key) ? this.map1 : this.map2).has(key);
},
delete: function (key) {
return (isPrimitive(key) ? this.map1 : this.map2).delete(key);
}
});
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
function isPrimitive(value) {
return Object(value) !== value;
}
However, I wouldn't recommend it because it adds a layer of abstraction, making your code slower.
Upvotes: 2