A recent passive-aggressive twitter message from me:
Oh yeah. Keep on bind()'ing folks, because bind() rocks. http://bit.ly/aPyfuV
In case you can't tell, the message is facetious.
bind() does not actually rock.
bind(), this post isn't going to make much sense, but if you're curious anyway, see Prototype's documentation for their
Let's look at what
bind() is actually doing:
- associates an object to use as
this(aka the receiver) when the function is invoked
- associates additional parameters to be passed to the function when the function is invoked (aka currying).
bind() does this by returning a new function, which when invoked, arranges for the object you want as the receiver and the additional parameters to be passed to the original function. It hides these values in the newly created function, associated with the closure the function was returned from.
bind() is frequently used for callbacks and event handlers, which typically allow you to pass a function as the callback, but don't provide a way to specify the receiver or additional parameters for the actual call to the callback.
bind() not rock?
gets in the way of debugging
Take a look at the bug I linked to in the Twitter message above: Bug 40080: Web Inspector: better Function.prototype.bind for the internal code
The bug concerns providing a better debug story around functions that have been
bind()'d. Because the current story isn't very pretty. When you stumble upon a
bind()'d function in the debugger, you see the source for the
bind()function itself, which isn't what you want to see. In addition the resulting bound function is usually anonymous, which means your stack traces, profile reports, etc, will be filled with (anonymous function) entries.
bind()creates a new function object every time it's called. Not just a string, or empty object, or array. A function. Which I'm guessing is more expensive than simpler objects. [yes, I should do some measurements.] A closure is also created and associated with the function (which is where the receiver and curried arguments are stored). More garbage.
Now imagine that, for whatever reason, you need to add and then remove a callback frequently, over and over again, for some reason. Or set a timer over and over again. And you're using
bind(). Think of the garbage you're creating.
Alex Russell posted some thoughts on
bind()in a post to the es-discuss mailing list (item 3).
So why does this [using
bind()] suck? Two reasons: it's long-ish to type, and it doesn't do what the dot operator does -- i.e., return the same function object every time.
It is longer, and therefore yuckier. It's also often not DRY (
setTimeout( obj.method.bind(obj, "John"), 100 );
The second point is explained by Alex in his post:
Many functions, both in the DOM and in the library, accept functions as arguments. ES5 provides a bind() method that can ease the use of these functions when the passed method comes from an object. This is, however, inconvenient. E.g.:
node.addEventListener("click", obj.method.bind(obj, ...));Also, insufficient. Event listeners can't be detached when using Function.prototype.bind:
// doesn't do what it looks like it should node.removeEventListener("click", obj.method.bind(obj, ...));
So the trick here, if you want to use
removeEventListener(), is that you have to store the result of a single call to
bind()and then use that value in subsequent
removeEventListener()paired calls. Alex's post suggests a new language feature to work around this (btw, I'm not in favor of his proposed language feature).
why are we in a bind with
It's pretty obvious to see how we got to the point where you need to use
bind() in your code today.
And so the places that take callbacks, like
onload handlers, etc, didn't really have a need for you to specify the receiver of of the callback when it was invoked. The receiver was always ... well, whatever it was for your callback.
The problem is, there's no where to put the receiver; all the pre-existing callback patterns just allowed the use of a function parameter. The trick with
bind() is that it attaches the receiver, and possibly curry'd arguments, to an invisible bag wrapped around a newly created function which is a delegated version of your callback function. Nature will find a way.
how can we fix the evils of
For me, the root of the problem is that we're passing the method receiver in a secondary channel, the bound function. So, stop doing that. Pass it explicitly.
Let's play with changing the
addEventListener() function to accommodate a new
receiver parameter. Here's the current function signature:
target.addEventListener(type, listener, useCapture)
We can add the
receiver parameter to the end of the function:
target.addEventListener(type, listener, useCapture, receiver)
or we could allow
receiver to be combined together in an array and used where the existing
listener value is today:
target.addEventListener(type, [receiver, listener], useCapture)
This second flavor tastes better to me.
what does the fix smell like?
Let's compare the code. For the examples below,
obj is the receiver of the callback,
callback() is a method available on the
Using ECMAScript 5's
Here's the four-arg
node.addEventListener("click", obj.callback, false, obj)
Here's the two-element-array-listener
node.addEventListener("click", [obj, obj.callback])
These examples could be made even DRYer, if instead of passing a function reference, you pass a string, which will be used as a property name to obtain the function from the receiver object:
node.addEventListener("click", "callback", false, obj) node.addEventListener("click", [obj, "callback"])
In terms of being able to handle the
removeEventListener() case as well, when using the ECMAScript 5 version of
bind() you would have to arrange to store a copy of the bound function, so you can send the exact same function both
removeEventListener(). My proposed versions could do a compare against the parameters or array elements, allowing you to use the exact same parameters on
In other words, here's how you do it in ECMAScript 5:
var boundFunction = obj.callback.bind(obj) node.addEventListener("click", boundFunction) ... node.removeEventListener("click", boundFunction)
And here's how you do it with my proposal:
node.addEventListener("click", [obj, "callback"]) ... node.removeEventListener("click", [obj, "callback"])
This invocation pattern works the same for the other form of call that I proposed.
Curried arguments can be handled the same way the
receiver parameter is handled; passed as additional arguments to
addEventListener() (not needed for
removeEventListener()?), or an additional element in the array where the
listener argument was previously used. One simplification would be to allow a single curried argument - other callback systems typically refer to this as
clientData - rather than deal with a variadic list. It's simple enough to combine multiple curried arguments into an object or array for use as a
bind() isn't always evil
Although I've spent this entire post complaining about
bind(), I will acknowledge it's power and usefulness. Particularly in meta-programming and function programming.
My primary complaint is having to use
bind() is something pedestrian as callbacks. That's too much.
the obligatory Smalltalk reference
My views on this subject are biased by my exposure to OTI/IBM Smalltalk. Read up on the Callbacks section on page 150 of "IBM Smalltalk: Programmer's Reference".
You'll note that my suggestion here is no different than the
addCallback:receiver:selector:clientData: method described in that manual, just a shorter name.