To bind or not to bind, that is in Function.prototype

Friday, September 2, 2011 Posted by Ruslan Matveev
Do you know anything about Function.prototype.bind? If you don't then you can get yourself familiar with it by reading this article on MDN. It's pretty hard to guess what it's used for, because the description is very complicated (at least for me) if you try to follow it:

Creates a new function that, when called, itself calls this function in the context of the provided this value, with a given sequence of arguments preceding any provided when the new function was called.

Creates new function, when called? itself calls? this function? What? I'm lost... To make it easier to understand let's look at the following code snippet:

// will alert [object DOMWindow]
(function foobar() {
    alert(this);
})();

We define a function, we call it, and alert this which is going to be a reference to the window object in case if you run this snippet in the global context, it's clear. But what if we need "this" reference to be pointing to something else? Following snippet might look very familiar for you:

// will alert 'something else'
(function foobar() {
    alert(this);
}.call('something else'));

This works great, but is it really necessary to use "Function.prototype.call" every time when we need to call function with different "this"? No it's not and that is exactly what "Function.prototype.bind" was invented for:

// define a function
var foobar = function() {
    alert(this);
};
// will alert [object DOMWindow]
foobar();
// bind function to different "this"
foobar = foobar.bind('always something else');
// will alert 'always something else'
foobar();

Now we only need to bind it once, and each call to "foobar" will automatically use our "always something else" string as "this".

If you try to run this code in your browser, you'll most likely get an exception, saying that "foobar.bind" is not a function (unless you're running on latest google chrome). This is because it's a feature of ECMAScript 5th edition, and there are not too many browsers that supports this natively. But the good thing is that we can emulate it's functionality by defining it ourselves. All you need to do is to insert following code snippet before the rest of your scripts:

// Function.prototype.bind implementation
if (!('bind' in Function.prototype)) {
    Function.prototype.bind = function() {
        var funcObj = this;
        var extraArgs = Array.prototype.slice.call(arguments);
        var thisObj = extraArgs.shift();
        return function() {
            return funcObj.apply(thisObj, extraArgs.concat(
                Array.prototype.slice.call(arguments)
            ));
        };
    };
}

Double bind problem

I'm not actually sure, if it's a bug or a feature (some people claim that it's a feature, since it's described in the official ECMAScript documentation) but calling ".bind" on the function that has been produced by another ".bind" call, will give you very unexpected results. Take a look on the following snippet:

// define a function
var foobar = function() {
    alert(this);
};
// will alert [object DOMWindow]
foobar();
// bind function to different "this"
foobar = foobar.bind('always something else');
// will alert 'always something else'
foobar();
// re - bind our function
foobar = foobar.bind('I want to replace this');
// will alert... 'always something else'
foobar();

So why is that?  Why last call to "foobar" still alerts "always something else"? I will either expect it to produce "I want to replace this" or throw an exception, but instead it just keeps it silent and second ".bind" call does nothing... No way! Can you imagine that you call something but it does nothing? Well that is what happens when you try to call ".bind" on the function that has been produced by another ".bind" call.

This wouldn't be that important for me if I wouldn't spend half a day on debugging the issue caused by this "anti social" behavior. So what happened in my case is that my code was using some let's say externally defined function, that I was trying to bind and then call. What I didn't know is that externally defined function has been produced by another ".bind" call, and all my attempts to replace it's "this" was doing nothing:

// somewhere in library.js...
var doEverythingFunction = function() {
    alert('You will spend all your ' + this + ' for nothing');
}.bind('money');

// somewhere in the code that uses library.js
doEverythingFunction.bind('life')();

So every time when I was rebinding and calling "doEverythingFunction" in my own code, the result was always "You will spend all your money for nothing". This is very sad because in real life situation you might not have an access to the external function in order to change it's behavior, so this is something that you'll have to keep in mind while you are dealing with Function.prototype.bind.

In fact, that was very unfortunate discovery for me, not only because it's not possible to replace "this" by "double binding", but because as I have already mentioned there was no exceptions, nothing that could give me an idea of what is actually going wrong. Is there any way to fix it? Well not to fix, because fix (if this is a bug) has to be done by ECMAScript team, but at least work around this problem?

Double bind workaround

Obviously I couldn't not just apply my workaround on Function.prototype.bind, because that might potentially break some other code that relies on the fact that "double bind" does nothing (though I'm not sure that such code exists). So what I had to do, is to go and build my own bind with blackjack and hookers, that I would use in my code only. That is how it looks like:

// given the fact that
if (!('bind' in Function.prototype)) {
    Function.prototype.bind = function() {
        var funcObj = this;
        var extraArgs = Array.prototype.slice.call(arguments);
        var thisObj = extraArgs.shift();
        return function() {
            return funcObj.apply(thisObj, extraArgs.concat(
                Array.prototype.slice.call(arguments)
            ));
        };
    };
}

// my own bind that supports double binding
function bind(functionObject) {
    // do regular bind
    var result = functionObject.bind.apply(
        functionObject,
        Array.prototype.slice.call(arguments, 1)
    );
    // overwrite bind by the one that will use
    // original function instead of it's binded version
    result.bind = function() {
        var args = Array.prototype.slice.call(arguments);
        return Function.prototype.bind.apply(functionObject, args);
    };
    // return binded function
    return result;
}

So from now on, instead of using someFunc.bind(...), we will use bind(someFunc, ...):

// somewhere in library.js...
var doEverythingFunction = bind(function() {
    alert('You will spend all your ' + this + ' for nothing');
}, 'money');

// somewhere in the code that uses library.js
bind(doEverythingFunction, 'life')();

It might not be perfect, and it is less convenient than simple myFunction.bind but still more convenient than doing myFunction.call every time when you need to replace "this" and at the end it serves it's purpose. That means it's great success!

Post a Comment