Function.prototype.bind Thursday, 11 September 2008

Function 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

  1. 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.

  2. Base2 bind

    Second performance-wise.

    Requires only a top level _slice function (trivial), and performs a strategy for extra arguments.

  3. 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.

  4. Ext-js Function.prototype.createDelegate

    Performance was slow. This function requires no importing of external functions.

  5. Mark Miller's bind

    Requires 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.

  6. Prototype's Function.prototype.bind

    Performance was fair. Requires several extra functions + browser detection. The function is used very heavily internally.

  7. Mootools Function.prototype.bind

    Performance 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 $family property, and makes other changes to Array.prototype.

  8. YUI 3 bind

    Performance 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:

Posted by default at 5:20 PM in JavaScript

 

 

[Trackback URL for this entry]

Comment: quarkdoll at Fri, 12 Sep 3:57 AM
(ya got a little paste happy - the article repeats itself)
Comment: quarkdoll at Fri, 12 Sep 4:21 AM
Did you really check what $type() is returning in MooTools 1.2? (because it is returning array for MDC literal arrays, MDC class constructed arrays, and MooTool's $$ operator)... :- /
Comment: Daniel Buchner at Fri, 12 Sep 10:01 AM
You are making claims about MooTools that are flat out wrong. Your implementation of the core $type function is flawed and so are your assertions of the library. As a user of the lib and others I really think you should be a little better scripting with, and understanding, all the libraries before you make such claims. Good job damaging OS developments efforts through code misuse, assumption, and ignorance! Completely making a fool of himself..."that's an interesting strategy Cotton, let's see how it works out for him!" - Dodgeball
Comment: Nathan White at Fri, 12 Sep 10:59 AM
Your comments about Mootools couldn't be further from the truth. $splat turns anything into an array. This is because Mootools simplifies the code following DRY principles and only uses 'apply' and not 'call'. If we look at your console.log yes at that moment it is an object but if we look at what happens in the next line an object will be simply become the first item in an array. So mootools handles fine. The other nice aspect of mootools is how they extend the prototype. Mootools also gives you 'attempt', 'bindWithEvent', 'delay' and 'periodical' all different variations of bind. 'create' is a generic bind creation function depending on the options, creates all kinds of neat binds. Very elegant code.

Before making comments like "No professional developer should allow himself to use such code for a real project." You really need to understand the code and understand how javascript works. It is also necessary to have a proper understanding of the philosophy of the framework that your examining.
Comment: Garrett at Fri, 12 Sep 1:33 PM
@Nathan,
What does DRY have to do with 'always using apply() and not call()'?

You are correct that, in this case, $splat will return an array with the array at position 0. It would look like:-
[ [] ]
.

The bind function of MooTools has one required parameter, bind, plus two optional parameters, args and event. Parameter args is expected to be an array by the code and this is documented:
http://docs.mootools.net/Native/Function/#Function:bind

@Daniel Buchner:'Your implementation of the core $type function is flawed'
$type is called by $splat. I did not 'implement' it.

@quarkdoll: 'it is returning array for MDC literal arrays'
Function $type returns 'array' for MDC literal arrays? MDC as in the "Mozilla Developer Center" documentation website? Did you assume there is a difference between an object created by an array literal or an array that is created otherwise?

Can you demonstrate one case where (typeof anything == 'array') is true?

We can see that here:
typeof []

Result: "object"

Function $type function will not ever return 'array', either.

Here is your '$type' function:
 

function $type(obj){
if (obj == undefined) return false;
if (obj.$family) return (obj.$family.name == 'number' &&
!isFinite(obj)) ? false : obj.$family.name;
if (obj.nodeName){
switch (obj.nodeType){
case 1: return 'element';
case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' :
'whitespace';
}
} else if (typeof obj.length == 'number'){
if (obj.callee) return 'arguments';
else if (obj.item) return 'collection';
}
return typeof obj;
};

Function $type will either return a boolean, a number, or a string. Is this part of the design philosophy, Nathan?

Where does $type return the string 'array'?

$type returns boolean false on line #1 if obj is either null or undefined. Next, if a $family property is found, if $family.name is not finite returns boolean false, otherwise the $family.name is returned. Then it does a check for a nodeName property, does a switch on a nodeType property and will return a string if the nodeType is 1 or 3 (presumably an Element or Text). Next it tries to get a 'length' property, where it can return either 'arguments' or 'collection'. FInally, it returns typeof obj.

In all the complaining, you MooTools people have provided 0 test cases and 0 examples.
Comment: Nathan White at Fri, 12 Sep 3:36 PM
its all about this line in $type()


if (obj.$family) return (obj.$family.name == 'number' &&


Arrays are wrapped into the Native Object. If you look at line 100 (http://github.com/mootools/mootools-core/tree/master/Source/Core/Core.js#L99) All native objects are extended if you look at line 42 a type is assigned to the native. So when you test they will return array.

You have several flaws with your test concerning mootools. 1. Native is included but never used. 2. Even if it was, it is missing other key elements like Native.typize.

I haven't had time to go through your code but from what I have seen it is broken and incomplete.
Comment: Garrett at Fri, 12 Sep 4:28 PM
The MooTools js library is now included, in its entirety, as part of the test.

MooTools' bind function depends on modification of Native prototype of Array, to add an Array.prototype.$family property.

MooTools and Prototype both modify build-ins' prototypes. The concern was that adding both libraries to the same page might effect the outcome of either. None of the other libraries' bind functions (except YUI's) required that the library be included in its entirety.

Moo is now passing two tests and with fairer performance.
Comment: Adam at Fri, 12 Sep 4:53 PM
It does not require 160k to get the oop module in YUI 3. I'm not sure how you could have come up with a number so far off the mark.

yui-base + oop: 9k

http://yui.yahooapis.com/combo?3.0.0pr1/build/yui-base/yui-base-min.js&3.0.0pr1/build/oop/oop-min.js

In addition, I've never seen the array module fail to load -- it is part of yui-base, and is loaded every time someone uses YUI 3.
Comment: Daniel Buchner at Fri, 12 Sep 5:27 PM
Dude, just run your stuff in an iframe like the SlickSpeed test. This way libs that mod native types would be able to overwrite each other.
Comment: Garrett at Fri, 12 Sep 5:37 PM
Here's where the 160k number comes from:

-rw-r--r-- 1 gs staff 11363 Sep 11 11:26 oop.js
-rw-r--r-- 1 gs staff 43893 Sep 11 11:01 yui-base.js
-rw-r--r-- 1 gs staff 109470 Aug 13 16:44 yui.js

oop.js..........12k
yui.js..........44k
yui-base.js....108k
-------------------
164k

You can point out that minification is going to cut the file size down. We all know that. The nicely-commented source code gets squashed.

However, for the purpose of the test, I used readable source code. Nobody's files got minified.

If each library had been included its entirety, the test page would be very heavy. Especially with Ext-js hogging up nearly 1mb. I drew the line at the bind function, plus whatever else is absolutely required by the bind function and nothing else.

In writing the test, I found that the YUI test would err on Y.Array. I figured that it was not loaded, but maybe the problem is caused by something else, including the possibility that I did not import or call use() properly.

I found the example here:-
http://developer.yahoo.com/yui/3/examples/yui/yui-more.html
load the modules and try:
javascript:alert(YUI.Array);

I am not clear on what |Y| is in Y.Array. YUI.Array and YAHOO.Array were both undefined.
Comment: Harald at Mon, 15 Sep 10:47 AM
Kinda strange framework review that you shuffled here. Who would use a framework depending on how many lines it uses for Function::bind. Like MooTools adds one powerful Function::create and just adds several aliases with different arguments (bind, pass, delay, periodical). Allowing arrays and non-array values for the "args" argument is called "Function overloading" or "Type polymorphism", benefits are well documented.

Your comment:

(not displayed)
 
 

 

*AnimTree
*Tabs
*GlideMenus
*DragLib