Function.prototype.bind Thursday, 11 September 2008
« Find Element Position | MainFunction Binding
A bind function wraps a function in a closure, storing a reference to the context argument in the containing scope.
This allows the bound function to run with a predetermined context.
Variable this
When a function is passed as a reference, it loses its
base object. When the unbound function is called,
the this value is the global object.
How Binding Works
By storing a reference to the desired object in a closure,
this argument can be bound.
In it's simplest form, bind looks like:-
Function.prototype.bind = function(context) { var fun = this; return function(){ return fun.apply(context, arguments); }; };
Why Binding is Useful
Binding is often necessary when passing function references. For example:-
var updater = { fetch : function() { alert(this.time++); }, time : 0 }; // setTimeout(updater.fetch, 500); setTimeout(updater.fetch.bind(updater), 500);
The commented-out call to setTimeout would result in a
call to
updater.fetch with the global object
for the this argument. this.time would
be undefined, and this.time++ would result in
NaN.
A bind function that does only binding accomplishes a trivial task. In most cases, a closure can just be used where binding is needed.
Binding in the Wild
Most JavaScript libraries handle binding internally. These libraries also include a partial apply for their bind function.
Partial Apply
Partial application is setting parameter values of a function call before it is called. A partial apply function usually looks like:-
/** * Return a function that prepends the * arguments to partial to this call and * appends any additional arguments. */ Function.prototype.partial = function() { var fun = this, preArgs = Array.prototype.slice.call(arguments); return function() { fun.apply(null, preArgs.concat.apply(preArgs, arguments)); }; };
This allows us to program in a dynamic, functional, less OO way. For example:
function setStyle(style, prop, value) { return style[prop] = value; } // Create a setBgColor function from // partial application of setStyle. var setBgColor = setStyle.partial(document.body.style, "background"); // Change the body's background color. setBgColor("red"); setBgColor("#0f0");
Disadvantage
Partial application requires an extra function call,
plus a call to concat
for the extra arguments.
Partial application can make debugging trickier, since there is an extra layer of indirection to the real method.
Bind + partial apply can be used to force the
this argument of a prototype method to
always be the of the instance.
This is inefficient and often leads to messy,
tangled function decomposition. Libraries that bind every method
do so out of ignorance of the language, and are best avoided.
EcmaScript New Language Feature
The forthcoming version of EcmaScript (now called EcmaScript Harmony)
will include
Function.prototype.bind(context). A native
bind should outperform any other bind function.
This was something Peter brought up for EcmaScript 4, but appears to be making way into the revised EcmaScript Harmony.
Mark Miller wrote out a "self-hosted" version of
EcmaScript's proposed Function.prototype.bind.
It is:-
Function.prototype.bind = function(self, var_args) { var thisFunc = this; var leftArgs = Array.slice(arguments, 1); return function(var_args) { var args = leftArgs.concat(Array.slice(arguments, 0)); return thisFunc.apply(self, args); }; };
After looking at the ES Harmony proposal, and looking at a
few versions of bind functions, I decided to write a better one
that does exactly what the ES Harmony's bind does, but with
greater efficiency than the current libraries offer, and whose,
length property was 1.
Although unnecessary, this is a welcome addition to the language. A native bind will outperform any user-defined bind function and will result in fewer closures.
The Rundown
Before I give a critique and rundown, I have a test.
Library Comparison Test
- Garrett's Bind
This bind was, by far, the most efficient in tests #1, and #4, and nearly ties Base 2 in test #2 (Base 2 was about .5 ms faster) . This function requires no additional code or functions.
- Base2
bindSecond performance-wise.
Requires only a top level
_slicefunction (trivial), and performs a strategy for extra arguments. - Dojo's hitch
Dojo was fast with pure bind, but slower with partial apply.
This function requires many other functions and has an additional complication of accepting strings and arguments of different order.
- Ext-js
Function.prototype.createDelegatePerformance was slow. This function requires no importing of external functions.
- Mark Miller's
bindRequires no external dependencies. While it gets a 10 for simplicity and aesthetics, this function was not as fast, and for pure bind (no partial apply) was not nearly as fast as it should be.
-
Prototype's
Function.prototype.bindPerformance was fair. Requires several extra functions + browser detection. The function is used very heavily internally.
- Mootools
Function.prototype.bindPerformance times were poor and the results for two tests were wrong.
were wrong.The entire mootools.js is required for the bind function. The library adds a
$familyproperty, and makes other changes toArray.prototype. - YUI 3
bindPerformance time was fair. The results for test#2 are wrong because the call's arguments are prepended, not appended. This is by design.
Having the call's arguments prepended is an unusual design decision. It might seem unintuitive, and confuse developers who are used to the more common version, as seen in Prototype, Base2, and the official EcmaScript proposal.
The amount of code YUI's bind depends on is staggering.
Pass the Parmeźan
Many of the libraries have long chains of function calls. A bind function does not need and should not require the inclusion of several other functions.
Prototype JS requires the $A function, which requires
Prototype.Browser to determine
which $A function. Browser detection has absolutely
no place in Function binding. ($A also calls
toArray conditionally, but that will not happen in this case.)
Dojo is almost as bad.
Dojo's hitch function has the arguments in reverse order and
requires dojo.global,
dojo._hitchArgs, dojo._toArray,
and dojo.isString.
Mootools has very strewn code. The broken bind function required an additional 108 lines of Mootools.
YUI is the most grandiose.
Function YUI.bind(f, o) is found in
"oop.js". File oop.js
requires over 120k of "prerequisite" files in
yui.js and yui-base.js,
coming to a total of over 160k. Just for bind. YUI 3 seems to suffer
from over-engineering and BUFD, which is typical in waterfall shops.
YUI's 'array' module did not seem to load or evaluate properly, so code from the yui-base file was copy-pasted.
Worth Using?
The best bind functions are fast, do not require other library functions, and are fairly simple.
But is a Bind Function Necessary?
No. Binding can be achieved with an inline closure where it is needed and partial application is not necessary.
Here is example 1, without using bind.
var updater = { fetch : function() { alert(this.time++); }, time : 0 }; setTimeout(function() { updater.fetch(); }, 500);
A native Function.prototype.bind will allow
for cleaner binding, without the need for creating
a closure. As native code, it will be faster and more reliable.
Function.prototype.bind is not necessary,
but is a welcome addition to the language.
Why All the Fuss?
Being aware of what libraries do and identifying and learning from the mistakes of libraries helps developers avoid such mistakes by learning what the library does. Developers do not need the burden of large, tangled, and often buggy library dependencies.
Look at The Code
What the library's bind function does and how the library uses that function internally is a step to take in assessing the library's quality.
A developer can make a more responsible and professional choice by avoiding a library that makes heavy use of a slow-spaghetti bind function.
My Version
The following function is the fastest bind function for pure bind (no partial apply). It is more than three times as fast as Base2, the second fastest bind function tested here.
Here is my version of the bind function.
/** * @param {Object} context the 'this' value to be used. * @param {arguments} [1..n] optional arguments that are * prepended to returned function's call. * @return {Function} a function that applies the original * function with 'context' as the thisArg. */ Function.prototype.bind = function(context){ var fn = this, ap, concat, args, isPartial = arguments.length > 1; // Strategy 1: just bind, not a partialApply if(!isPartial) { return function() { if(arguments.length !== 0) { return fn.apply(context, arguments); } else { return fn.call(context); // faster in Firefox. } }; } else { // Strategy 2: partialApply ap = Array.prototype, args = ap.slice.call(arguments, 1); concat = ap.concat; return function() { return fn.apply(context, arguments.length === 0 ? args : concat.apply(args, arguments)); }; } };
This function was not included in APE because it was not needed. This function may be used by libraries who wish to continue using a bind function with the benefit of faster performance.
Technorati Tags: JavaScript ECMAScript 4
