Understanding JavaScript’s Function.prototype.bind

Go to Source


  

Function binding is most probably your least concern when beginning with JavaScript, but when you realize that you need a solution to the problem of how to keep the context of this within another function, then you might not realize that what you actually need is Function.prototype.bind().

The first time you hit upon the problem, you might be inclined to set this to a variable that you can reference when you change context. Many people opt for self, _this or sometimes context as a variable name. They’re all usable and nothing is wrong with doing that, but there is a better, dedicated way.

Jack Archibald tweets about caching this:

Ohhhh I would do anything for scope, but I won't do that = this

— Jake Archibald (@jaffathecake) February 20, 2013

It should have been more apparent to me when Sindre Sorhus spelled it out:

@benhowdle $this for jQuery, for plain JS i don't, use .bind()

— Sindre Sorhus (@sindresorhus) February 22, 2013

I ignored this wise advice for many months.

What Problem Are We Actually Looking To Solve?

Here is sample code in which one could be forgiven for caching the context to a variable:


var myObj = {

    specialFunction: function () {

    },

    anotherSpecialFunction: function () {

    },

    getAsyncData: function (cb) {
        cb();
    },

    render: function () {
        var that = this;
        this.getAsyncData(function () {
            that.specialFunction();
            that.anotherSpecialFunction();
        });
    }
};

myObj.render();

If we had left our function calls as this.specialFunction(), then we would have received the following error:


Uncaught TypeError: Object [object global] has no method 'specialFunction'

We need to keep the context of the myObj object referenced for when the callback function is called. Calling that.specialFunction() enables us to maintain that context and correctly execute our function. However, this could be neatened somewhat by using Function.prototype.bind().

Let’s rewrite our example:


render: function () {

    this.getAsyncData(function () {

        this.specialFunction();

        this.anotherSpecialFunction();

    }.bind(this));

}

What Did We Just Do?

Well, .bind() simply creates a new function that, when called, has its this keyword set to the provided value. So, we pass our desired context, this (which is myObj), into the .bind() function. Then, when the callback function is executed, this references myObj.

If you’re interested to see what Function.prototype.bind() might look like and what its doing internally, here is a very simple example:


Function.prototype.bind = function (scope) {
    var fn = this;
    return function () {
        return fn.apply(scope);
    };
}

And here is a very simple use case:


var foo = {
    x: 3
}

var bar = function(){
    console.log(this.x);
}

bar(); // undefined

var boundFunc = bar.bind(foo);

boundFunc(); // 3

We’ve created a new function that, when executed, has its this set to foo — not the global scope, as in the example where we called bar();.

Browser Support

Browser Version support
Chrome 7
Firefox (Gecko) 4.0 (2)
Internet Explorer 9
Opera 11.60
Safari 5.1.4

As you can see, unfortunately, Function.prototype.bind isn’t supported in Internet Explorer 8 and below, so you’ll run into problems if you try to use it without a fallback.

Luckily, Mozilla Developer Network, being the wonderful resource it is, provides a rock-solid alternative if the browser hasn’t implemented the native .bind() method:


if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instance of fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Patterns For Usage

When learning something, I find it useful not only to thoroughly learn the concept, but to see it applied to what I’m currently working on (or something close to it). Hopefully, some of the examples below can be applied to your code or to problems you’re facing.

Click Handlers

One use is to track clicks (or to perform an action after a click) that might require us to store information in an object, like so:


var logger = {
    x: 0,       
    updateCount: function(){
        this.x++;
        console.log(this.x);
    }
}

We might assign click handlers like this and subsequently call the updateCount() in our logger object:


document.querySelector('button').addEventListener('click', function(){
    logger.updateCount();
});

But we’ve had to create an unnecessary anonymous function to allow the this keyword to stand correct in the updateCount() function.

This could be neatened up, like so:


document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));

We’ve used the subtly handy .bind() function to create a new function and then set the scope to be bound to the logger object.

setTimeout

If you’ve ever worked with templating engines (such as Handlebars) or especially with certain MV* frameworks (I can only speak of Backbone.js from experience), then you might be aware of the problem that occurs when you render the template but want to access the new DOM nodes immediately after your render call.

Suppose we try to instantiate a jQuery plugin:


var myView = {

    template: '/* a template string containing our <select /> */',

    $el: $('#content'),

    afterRender: function () {
        this.$el.find('select').myPlugin();
    },

    render: function () {
        this.$el.html(this.template());
        this.afterRender();
    }
}

myView.render();

You might find that it works — but not all the time. Therein lies the problem. It’s a rat race: Whatever happens to get there first wins. Sometimes it’s the render, sometimes it’s the plugin’s instantiation.

Now, unbeknownst to some, we can use a slight hack with setTimeout().

With a slight rewrite, we can safely instantiate our jQuery plugin once the DOM nodes are present:


//

    afterRender: function () {
        this.$el.find('select').myPlugin();
    },

    render: function () {
        this.$el.html(this.template());
        setTimeout(this.afterRender, 0);        
    }

// 

However, we will receive the trusty message that the function .afterRender() cannot be found.

What we do, then, is throw our .bind() into the mix:


//

    afterRender: function () {
        this.$el.find('select').myPlugin();
    },

    render: function () {
        this.$el.html(this.template());
        setTimeout(this.afterRender.bind(this), 0);        
    }

// 

Now, our afterRender() function will execute in the correct context.

Tidier Event Binding With querySelectorAll

The DOM API improved significantly once it included such useful methods as querySelector, querySelectorAll and the classList API, to name a few of the many.

However, there’s not really a way to natively add events to a NodeList as of yet. So, we end up stealing the forEach function from the Array.prototype to loop, like so:


Array.prototype.forEach.call(document.querySelectorAll('.klasses'), function(el){
    el.addEventListener('click', someFunction);
});

We can do better than that, though, with our friend .bind():


var unboundForEach = Array.prototype.forEach,
    forEach = Function.prototype.call.bind(unboundForEach);

forEach(document.querySelectorAll('.klasses'), function (el) {
    el.addEventListener('click', someFunction);
});

We now have a tidy method to loop our DOM nodes.

Conclusion

As you can see, the .bind() function can be subtly included for many different purposes, as well as to neaten existing code. Hopefully, this overview has given you what you need to add .bind() to your own code (if necessary!) and to harness the power of transforming the value of this.

(al, il)


© Ben Howdle for Smashing Magazine, 2014.