JavaScript: what's the point of bind()?

fluffynuts

Davyd McColl

Posted on March 25, 2020

JavaScript: what's the point of bind()?

A fellow at work is currently working through a JavaScript course. He shared the following snippet of code:

let post = {
    id: 1,
};

let UI = {
    render: function(){
        console.log(this.id);
    }
};

let r = UI.render.bind(post);
r();

And asked the question:

I still don’t fully understand what the point of the bind keyword is and what it is supposed to achieve, maybe you can clarify? Why not just call the object directly with “post.id” instead of “this.id” and then
binding it in a third function.

Which I thought was a good question -- and I'm sure others have asked it. I'm quite sure I remember asking it myself.

Functions are first-class citizens

First off, we have to remember that functions in JavaScript are first-class citizens. This means that we can pass them around like any other object:

function modifyAndLog(startingNumber, modifyingFunction) {
  const result = modifyingFunction(startingNumber);
  console.log(`Modified ${startingNumber} to get result ${result}`);
  return result;
}

function double(number) {
  return number * 2;
}

function doubleAndLog(number) {
  return modifyAndLog(number, double);
}

doubleAndLog(2); // prints out a message and returns 4

See how we can pass functions around like any other object? This is also why the following function declaration syntax works:

var add = function(a, b) {
  return a + b;
}

(which, of course, is equivalent to the following):

var add = (a, b) => a + b;

but let's forget about arrow functions (for now) as they introduce a different behavior for .bind() (for good reason). I'll circle back to this later. Also, please bear the following in mind with respect to the above syntax:

[1] function variable declarations are hoisted to the top of the file, however the assignment only happens when that code is run:

var a = function() {
    b(); // will error
};
a();
var b = function() {
    console.log("b called");
};

[2] function variable declarations like the above create anonymous functions, which traditionally gave horrid stack traces when they eventually error: instead of seeing that you have an error in function a,you may see just the line number, and an anonymous function specified. Modern browsers and NodeJS versions give better messages, but I still recommend that if you absolutely must use function variable declarations like this, that you do

var a = function a() {
    b(); // will error
};
a();
var b = function b() {
    console.log("b called");
};

Rather prefer to explicitly define your functions in the form:

function a() {
}

and "var them off" if you really need to:

function a(fn) {
    console.log(fn());
}

function b() {
    return "b called";
}

var bFunction = b; // this var is unnecessary: just here to illustrate
a(bFunction); // prints "b called"

Functions are new, every time

Consider this code:

function Foo() {
  this.add = function(a, b) {
    return a + b;
  };
  this.add2 = function(a) {
    return this.add(a, 2)
  };
}

var foo1 = new Foo();
var foo2 = new Foo();

console.log(foo1.add === foo2.add); // logs false

This is ye olde syntax for object creation. This, kids, is what we used before the es6 class syntax (also used by TypeScript) became available.

Note here that the Foo function:

  1. Creates a new anonymous function
  2. Assigns that to the doStuff property on the new'd up object

This is why prototypes exist as they do: in a memory-constrained environment, we don't want code like the above creating many copies of (essentially) the same function. So a better solution to the above would be:

function Foo() {
}
Foo.prototype = {
  add: function(a, b) {
    return a + b;
  },
  add2: function(a) {
    return this.add(a, 2);
  }
};

function makeFoo() {
    return new Foo();
}
var foo1 = makeFoo();
var foo2 = makeFoo();
console.log(foo1.doStuff === foo2.doStuff); // logs true

In the above, we save memory (and compilation time) by essentially re-using the same function pointer every time we new up a Foo. This especially makes a difference if you're newing up hundreds of Foos.

Interestingly, because of the "light-copy" nature of the prototype, modifying the prototype later will apply that modification to every instance of the prototype, even existing ones:

var foo = makeFoo();
Foo.prototype.add = function(a, b) {
    // introduce an off-by-one error
    return a + b + 1;
}
console.log(foo.add(1, 2)); // prints 4, not 3

JavaScript has a history

JavaScript was created by Brendan Eich in a very short time period to embed in Netscape Navigator around 1995. Every time we find something "odd" with JavaScript, bear in mind that:

  1. It was created on a shoestring time-budget (about 2 weeks)
  2. It had to work on much more constrained machines than today -- even the phone in your pocket is orders of magnitude more powerful, computationally and memory-wise

Prototypes were an elegant solution to the above problem. In essence, the above prototype code could be written as:

function makeFoo() {
  var result = {};
  result.add = add.bind(result);
  result.add2 = add2.bind(result);
  return result;
}
function add(a, b) {
  return a + b;
}
function add2(a) {
  return this.add(a, 2);
}

The .bind calls are necessary so that add2 has a correct reference for this when it is called.

In reality, the mechanism is a little more complex, and involves calls on this.{whatever} searching through the prototype chain for methods. But the example above illustrates a possible solution.

Aside: Also remember that you can set the this context for any function by using either .call or .apply:

function add2(a) {
  return this.add(a, 2);
}
const adder = {
  add: function(a, b) {
    return a + b;
  }
};
const bound = add2.bind(adder);
console.log(bound(2)); // logs 4
console.log(add2.call(adder, 6)); // logs 8
console.log(add2.apply(adder, [10])); // logs 12

The only difference between .call and .apply is that .apply takes an array of arguments, where .call takes arguments as parameters. This makes it useful when you want to programmatically build up arguments for a function -- you put them in an array and use .apply

.bind can be essentially rewritten as:

function bind(ctx, fn) {
    return function() {
        return fn.apply(ctx, arguments);
    };
}
const adder = {
    add: function(a, b) {
        return a + b;
    }
};

function add2(a) {
    return this.add(a, 2);
}
var bound = bind(adder, add2);
console.log(bound(13)); // prints 15

What's this anyway?

Every object-oriented programming (OO) language that I've ever encountered has the concept of the current object context within member methods, often called this, though VB used Me and in Python it can be whatever you like, but the convention is self, as it is in Ruby, perhaps a nod to Python which came 5 years earlier? PHP and ITcl use $this, which is really just this in their own dialect 😆

Consider C#:

public class Adder
{
    public int Add2(int a)
    {
        // note that `this` can be dropped in C#,
        // because the compiler can infer that
        // the Add method exists on `this`. But I've
        // included it here for clarity
        return this.Add(a, 2);
    }

    public int Add(int a, int b)
    {
        return a + b;
    }
}

Python is more explicit: members must be declared taking in as their first argument the context on which to operate.

class Adder:
  def add(self, a, b):
    return a + b

  def add2(self, a):
    return self.add(a, 2)

foo = Adder()
print(foo.add2(4)) # prints 6

It turns out that Python is just being very explicit about what other runtimes and languages do anyway.
For example, if we were to use reflection to invoke Add2 on the C# class above, it could be:

var adder = new Adder();
var add2Method = typeof(Adder).GetMethod(nameof(Adder.Add2));
Console.WriteLine((int)add2Method.Invoke(adder, new object[] { 4 }); // prints 6

The .net runtime is doing the same thing when it invokes methods. Similarly, C++ methods are compiled in such a way as to take the context to become this as the first parameter, and calls to that method
are compiled to explicitly provide this context. (At least, it was, last I checked).

The concept of this is "transparent magic" to the programmer in most OO languages.

Coming back to answer the original question

There are several circumstances where being able to bind is advantageous

Passing a member as a callback

Imagine this code, which does stuff with DOM elements:

class BodyClicked1 {
    _element;
    _clickedCount;

    constructor() {
        this._element = document.querySelector("body");
        this._clickedCount = 0;
        this._element.addEventListener("click", this.clicked.bind(this));
    }

    clicked(ev) {
        this._clickedCount++;
        console.log(`You've clicked me ${this._clickedCount} times`);
    }
}

Without the call to .bind, the DOM element would call the provided handler (eventHandler) without any context, so the line this._clickedCount++ would error, most likely with an error like this. is not a function. The DOM element doesn't have the context, so it can't know what to call. Of course, we could also rewrite the above as:

class BodyClicked2 {
    _element;
    _clickedCount;

    constructor() {
        this._element = document.querySelector("body");
        this._clickedCount = 0;
        this._element.addEventListener("click", ev => this.clicked(ev);
    }

    clicked(ev) {
        this._clickedCount++;
        console.log(`You've clicked me ${this._clickedCount} times`);
    }
}

But we should also unsubscribe when we're done, which we can't do without a reference to the original subscribing function, ie:

class BodyClicked3 {
    _element;
    _clickedCount;
    _clicked;

    constructor() {
        this._element = document.querySelector("body");
        this._clickedCount = 0;
        this._clicked = this.clicked.bind(this);
        this._element.addEventListener("click", this._clicked);
    }

    destroyed() {
        if (this._element) {
            this._element.removeEventListener("click", this._clicked);
            this._element = undefined;
        }
    }

    clicked(ev) {
        this._clickedCount++;
        console.log(`You've clicked me ${this._clickedCount} times`);
    }
}

If we didn't have the bound function, a call to removeEventListener wouldn't properly unsubscribe, because we'd be passing in a new function:

class BodyClicked4 {
    _element;
    _clickedCount;

    constructor() {
        this._element = document.querySelector("body");
        this._clickedCount = 0;
        this._element.addEventListener("click", this.clicked.bind(this));
    }

    destroyed() {
        if (this._element) {
            // doesn't work because it's not the exact same function we bound with
            this._element.removeEventListener("click", this.clicked.bind(this));
            this._element = undefined;
        }
    }

    clicked(ev) {
        this._clickedCount++;
        console.log(`You've clicked me ${this._clickedCount} times`);
    }
}

Frameworks like AngularJS

AngularJS has always been one of my favorite frameworks. Perhaps just because I learned it quite early on, or perhaps because it just did more and sat better with me than Knockout or CanJS did at the time. AngularJS is still kept up to date, so don't count it out. Personally, I find it more flexible than Angular, though both have their merits. Anyway, I'm not here to start The War Of The Angulars 😄 and anything new I write at the moment would probably be in Vue, so there's that 😛

If we have a look at how an AngularJS directive can be created:

angular.module("app")
    .directive("CustomElement", ["$scope", function() {
        return {
            restrict: "E",
            template: "<button ng-click='clicked'>Click me</button>",
            controller: function ($scope) {
                $scope.clickCount = 0;
                $scope.clicked = function() {
                    $scope.clickCount++;
                    alert(`you clicked the button ${$scope.clickCount} times!`);
                }
            }
        };
    }]);

With the above code, every time a custom-element is created, a brand-new clicked function is created, compiled and stored in memory. Not a big deal if this is only created a few times in the lifetime of the app, but if you have a bunch of repeated custom-element tags, you're paying CPU and memory that you don't have to. We can't use a prototype here because AngularJS simply calls the function we gave it with the dependencies we asked for. However, we can use .bind:

(function() {
    // use a closure to stop the function `clicked` from leaking out or being
    // overwritten elsewhere
    angular.module("app")
        .directive("CustomElement", ["$scope", function() {
            return {
                restrict: "E",
                template: "<button ng-click='clicked'>Click me</button>",
                controller: function ($scope) {
                    $scope.clickCount = 0;
                    $scope.clicked = function() {
                        $scope.clickCount++;
                        alert(`you clicked the button ${$scope.clickCount} times!`);
                    }
                }
            };
        }]);
    function clicked() {
        this.clickCount++;
        alert(`you clicked the button ${this.clickCount} times!`);
    }
})();

Whilst .bind produces a new function every time, the original function only has to be JIT'ed once -- the bound version is doing something like my example above, performing clicked.call with the provided context. In addition, our controller function can be kept shorter and neater -- we could even declare the methods in other files, eg if we were using es6 modules or AMD (eg Require).

Binding parameters

.bind isn't only useful for binding the context to use for this -- you can bind parameters too (essentially, currying):

function add(a, b) {
  return a + b;
}
var add2 = add.bind(null, 2);
console.log(add2(8)); // prints 10

note that we still need to provide an argument for the context -- above, I didn't really need a context, so I bound null

Arrow functions

I promised to come back to this...

Arrow functions are not just nice to look at -- they also introduce new behavior with respect to the context of a function.

Consider code like this:

class SomeService {
  fetchData() {
    return $.get("/data").then(function(result) {
      return this.process(result);
    });
  }
  process(data) {
    // does some stuff with the data
  }
}

I'm sure we were all caught out at some point with code like the above, and this not being quite what we expected. A simple solution is:

class SomeService {
  fetchData() {
    var self = this;
    return $.get("/data").then(function(result) {
      return self.process(result);
    });
  }
  process(data) {
    // does some stuff with the data
  }
}

See that var self = this; line at the top? That's an easy go-to as an alternative to using .bind(), which we could have, like so:

class SomeService {
  fetchData() {
    var bound = this.process.bind(this)
    return $.get("/data").then(function(result) {
      return bound(result);
    });
  }
  process(data) {
    // does some stuff with the data
  }
}
// or, shorter
class SomeService {
  fetchData() {
    return $.get("/data").then(
      this.process.bind(this)
    });
  }
  process(data) {
    // does some stuff with the data
  }
}

Both options are unnecessarily cumbersome, so the arrow function was introduced. Arrow functions cannot have their this binding changed -- they are assigned this from the scope in which they are declared. (They also don't have access to arguments or super).

Arrow functions make the code above simpler to read and understand:

class SomeService {
  fetchData() {
    return $.get("/data").then(
      result => this.process(result)
    );
  }
  process(data) {
    // does some stuff with the data
  }
}

When you can use arrow functions, I suggest you do, as they make reading the code a lot simpler for others. When you can't, there's always the trusty .bind()

Wrapping up

.bind is an essential tool in the JavaScript developer's toolbox. Understanding how (and when) to use it is vital to being able to write effective JavaScript. Understanding the nuts-and-bolts of .call and .apply is also good 😄

💖 💪 🙅 🚩
fluffynuts
Davyd McColl

Posted on March 25, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related