Install Using Any ..
- npm install lazybindall
- npm install sourcevault/lazybindall
- npm install gitlabs:ourcevault/lazybindall
- npm install @sourcevault/lazybindall
Type Signature
lazybindall :: ( ob , methds ) -> obmethds
-
ob <Object>
- Methods inmethds
get bound toob
. -
methds <Object>
- Object with props that are methods that we want to lock toob
-
obmethds <Object>
is an object whose props are defined bemethds
and have there context locked toob
.
Simple Example
var foo = {name:"sourcevault"}
var methds =
{
show:function()
{
console.log (this)
}
}
// ----------------------------------------------
var lazybindall = require ("lazybindall")
var bound = lazybindall ( foo , methds )
bound.show() // {name:"sourcevault"}
Locking context in Javascript can only happen through closures:
var foo = function (x)
{
this.value += x
}
lockFoo = function (state) // <- state is captured in the inner function
{
return function (x)
{
foo.call (state,x) // state gets captured from the outer function
}
}
state = {value:1}
lockedFoo = lockFoo (state)
lockedFoo(3)
console.log (state) // {value:4}
The functionality expressed above is well encapculated in the .bind
method that every function in javascript can call upon to lock context.
We can use bind
to create bindall
which is just a for loop for applying bind for multiple functions.
The usecase for bindall
is apparent when large number of methods with internal state dependency require exporting. The issue arises due to the consumer having liberty to attach any exported function to external objects causing mutation to the internal .this
variable - breaking any internal code that depends on .this
staying invariant.
// ./largeApi.js
// What a potential large API surface looks like
Ob = function()
{
this.resultList = []
return (this)
}
Ob.prototype.f1 = function()
{
var sum = 0
// .... /
this.resultList.push( sum )
// .... /
return (this)
}
Ob.prototype.f2 = function(){// .... /}
Ob.prototype.f3 = function(){// .... /}
// .... /
// .... /
// .... /
Ob.prototype.f100 = function(){// .... /}
module.exports = Ob
// test.js
var api = require ("./largeApi")
var _ = require ("underscore")
var instance = new api()
var boundApi = _.bindAll(instance)
setTimeout (boundApi.f1,1000) // [1] runs fine
setTimeout (instance.f1,1000)
// [2] TypeError: Cannot read property 'push' of undefined
// [2] ofcourse V8 vomited an error since .this of api.f1 points to global
ADDITIONAL READING:
-
underscore.js
's original implementation ofbindall
. -
Michael Fogus's book "Introducting Functional Programming with Underscore.js" has more details involing functional design patterns invovling
bindall
. -
Here is my take on
bindall
Using bindall
is perfectly fine for most application but pay attention to what happens at the margins. bindall
is making a copy of the data structure representing our module. If Fig 1 is the data structure showing our module, then bindall
is making a modified copy (copy not shown in Fig 1).
Fig 1 is a graphical representation of the data structure of large exported module with internal state stored in an object.
The inefficiency in constantly making clones becomes more apparent when we consider the fact that the consumer might be interested in making n instances each attached to some unique object (Fig 2).
Fig 2 - n instances with unique object (state) for each instance.
(bindall
clone not shown)
It's not difficult to see how applying bindall
to each module layer could quickly cause memory issues. APIs involving the DOM have large surface area for each document object
(ie. onclick, onhover, onmouseover ...).
There is something we notice when handling large APIs, that can give us a clue as to how we can optimize - we rarely use all possible methods for each instance (Fig 3). The reason we expose large API surfaces is to expose all possible functionalities, so that it works under varied use, even as creator we do not expect complete method utilization at every use - this is where it pays to be lazy.
Fig 3 - In practice most methods calls will be sparsely distributed - bindall
makes more sense for small APIs with dense use, while lazy binding allocates memory only when required.
What we could do instead of an eagar bindall
is to only bind
when our function leaves the comfort of its parent object.
We can do this by taking advantage of ES6 proxy getter hooks to lock context - instead of retroactively binding and then exporting it.
...for 10,000 object with 9 methods
Method | Total Memory (MB) | Time (millisecond) |
---|---|---|
.prototype |
6 | 31 |
lazy closure | 9 | 54 |
eagar closure | 49 | 6,172 |
NOTES:
-
will be interesting to see how benchmark values change against increasing number of methods.
-
.prototype
is always going to be fastest because there is no closure creation - so state cannot be even passed to the event loop. It helps create a lower bound. -
Time and memory is related for our benchmark since we are are keeping CPU intensive tasks constant, hence it can be assumed that time is a reflection of how long it takes the system to allocate memory, giving eagar closures both a memory, and time penalty.
-
benchmark source is
src/benchmark.ls
, run usingnode dist/benchmark.js
-
hardware used -
i7-3667U CPU (2 GHz 2.5 GHz), (x64, 7.86 RAM)
-
More user friendly API simarly to
sourcevault/bindall
-
most.js
integration
- Initial
0.0.1
release with a single exported function.