Usage of bind method in object-oriented javascript
With PHP being an object-oriented language with classes, the whole concept of the context of a class and the meaning of the variable $this is fairly easy to understand. Wherever you are in your class, as long as it’s not inside a static method, $this will always refer to the instance of the class. JavaScript, however, does not use classes. The whole notion of this becomes harder to grasp, and when writing basic JavaScript with jQuery or Vanilla rather than a framework like React or Angular, this seems to prevent some developers from taking a more object-oriented approach.
CONTEXT IN JAVASCRIPT
Without classes (it’s only syntactic sugar when using ES6), the workaround for instantiating new objects is with the usage of a function. A function in Javascript assumes both the roles of a function and of a class to instantiate new objects.
Suppose we want to instantiate a new Person. The way to do it in JavaScript would be something along the lines of:
function Person (name) { this.name = name; this.greet = function () { console.log("Hello, my name is " + this.name) }; } var bob = new Person ('Bob'); bob.greet();
The output you would get from the console would be Hello, my name is Bob. Now let’s add a method to change Bob’s name (sure enough, this.name in this example is public and could be directly changed, but bear with me).
function Person (name) { this.name = name;
this.greet = function () {
console.log(“Hello, my name is ” + this.name);
};
this.changeName = function (newName)
}
var bob = new Person(‘Bob’);
bob.changeName(‘John’);
bob.greet();
In this case, Bob would now greet us with Hello, my name is John as we would expect. Now here is an interesting property with function in JavaScript: The this keyword refers to its owner. In the case of our object bob, the method changeName being defined within the class Person, this was referring to the intended instance.
To see this in action, we are going to remove changeName from our class and define it outside of it.
// changeName is now outside the class function changeName (newName) { this.name = newName; }
function Person (name) {
this.name = name;
this.greet = function () {
console.log(‘Hello, my name is ‘ + this.name);
};
}
var bob = new Person(‘Bob’);
// we assign to the bob instance it’s own copy the changeName function
bob.changeName = changeName;
bob.changeName(‘John’);
bob.greet(); // “Hello my name is John”
Here again, the result of Bob greeting us will be Hello, my name is John. The reason being in JavaScript, this refers to the owner of the method. What we did here is we assigned to the bob instance its own changeName method, which means that this was referring to the object bob and was thus able to change its name.
Let’s put back changeName in the class, and try a different approach. If changeName belongs to our bob object, what would happen if we instead passed it as a callback? We can do this by creating another method in our class, say doSomethingAsync, that takes a callback and a value as arguments, and execute that method with that value.
function Person (name) { this.name = name; this.greet = function () { console.log('Hello, my name is ' + this.name); }; this.changeName = function (newName) { this.name = newName; } this.doSomethingAsync = function (callback, newName) { callback(newName); } } var bob = new Person('Bob'); bob.doSomethingAsync(bob.changeName, 'John'); bob.greet(); // “Hello my name is Bob” (not the result we were expecting)'
What we are doing here is we are passing our class method changeName as a callback to be executed by our other instance method doSomethingAsync. This means that once again, Bob’s name will be changed to John, right? Well, no. Why is that?
A callback is a function passed as an argument to another function, and arguments being a copy of the value of a variable, what ends up happening is that our callback is now a copy of our instance method changeName and doesn’t have the same context anymore. In short, when you pass down a method as a callback, you lose the intended context in which you want it to be executed.
In order for our callback to be executed in the proper context, we have to find a way to bind our callback to it, and this is exactly what the bind method does. Let’s see it in action.
var bob = new Person('Bob'); bob.doSomethingAsync(bob.changeName.bind(bob), 'John'); bob.greet(); // “Hello my name is John” (now we get the expected result)'
We now have the intended result by binding our callback to bob. When you bind a certain object to a method, the context of that method will always be that object no matter who called it.
Using bind in an object-oriented approach
The great advantage of the bind method is that it can now make any of our methods reusable in different contexts. Let’s see this in action with a fairly simple example of an application we would want to build.
Suppose we have a div containing a span and a button.
0
Increment
We want the span to increment its number every time we click the button. If you want to make this happen in the most straightforward way, you may do something similar to
var count = parseInt($('#count').text(), 10); $('button').on('click', function () { count++; $('#count').text(count); });
Even though this would work, there are many issues with this. First of all, count is defined globally, and should only be defined in the context of our incrementer. Second, we search for the span element inside of the event handler, which means we will search for it every time we click the button. This is no good because searching in the DOM is costly and should be done as little as possible. Finally, we are defining an anonymous function for incrementing the value in the span, which is only usable when we trigger a click. But what if, later in the project, we would need to manually increment that value without having to click the button?
Instead, let’s take advantage of what we just learned about .bind and do this with an object-oriented approach by creating a class that will handle all of that logic. First of all, we set the count as well as all of our DOM elements to be members of that class.
function Incrementer () { this.count = 0; this.$el = $('#incrementer'); this.$countSpan = this.$el.find('#count'); this.$button = this.$el.find('button'); } var incrementer = new Incrementer();
Let’s now add our click handler to that class.
function Incrementer () { this.count = 0; this.$el = $('#incrementer'); this.$countSpan = this.$el.find('#count'); this.$button = this.$el.find('button');
this.$button.on('click', function () { this.count++; this.$countSpan.text(this.count); }); }
var incrementer = new Incrementer();
Before we go any further, this code will fail. Remember what we said about a function passed as a callback? Here, not only we are doing this without binding it but, this function being anonymous, it is actually not even defined within our class. Let’s fix it by doing two things: define the anonymous method in our class, then bind it.
function Incrementer () { this.count = 0; this.$el = $('#incrementer'); this.$countSpan = this.$el.find('#count'); this.$button = this.$el.find('button');
this.increment = function () {
this.count++;
this.$countSpan.text(this.count);
}
this.$button.on('click', this.increment.bind(this)); }
var incrementer = new Incrementer();
Now, this code will work. Let’s summarize the gains we’ve made compared to the first version of it. Our variable count and our DOM elements are in their own class and are no longer global. By having our DOM elements as members of that class, we no longer search and search again for them every time we click the button. With the usage of bind, we can pass an event handler as a callback while keeping the proper context, which means it can now be reusable. An example of its reusability would be that now, all we need to do if we want to increment the span without triggering a click would simply be by calling the increment method like so.
incrementer.increment();
CONCLUSION
The usage of the bind method allows any callback function to be executed in any context we would want to. This gives us a great deal of reusability for any method we create, and this is a very good first step toward an object-oriented approach in JavaScript without the use of any framework. As a result, you can write cleaner code without the usage of a framework, and having those practices will also give you a better understanding of the frameworks themselves.