Topic 17: Use of "bind"

This topic is only intended for students who are up-to-date. You must complete Topics 1-4, 6-8, and 11-12 before you attempt this. Also, you need to have completed the "Classes" section of Topic 9 to understand the examples.

"this" and callbacks

Subtleties with "this"

There are a number of subtleties with JavaScript this which you need to understand to work effectively with objects, and which make its use less straightforward than the use of this in other languages such as Java and C++. These subtleties are particularly important when it comes to callbacks.

"this" and the window object

Firstly, what does this refer to if it is not being used in the context of an object? For example, if the function below was run, what would be displayed?

function thisTest()
{
    alert(this);
}
On Firefox it displays:
[object Window]
The output demonstrates what this refers to when out of the context of an object. It refers to a "default object" which in a web browser is the window object, representing the browser window as a whole. So the example below would work too:
function thisTest()
{
    window.alert("alert is actually a method of the window object!");
    this.alert("... and here, *this* refers to the window so this works too!");
}
Both alerts are displayed. alert() is actually a method of the window object, but because the window object is the environment's "default" object, we can normally leave window out; it's included here just to illustrate the point. And because this also refers to the window object in this context, the second alert will be displayed too.

"this" and callbacks - the problem

The problem most commonly encountered with "this" is when it is being used in a callback function. As we have seen, a callback function is a function that does not run immediately, but at a later time, when some event has occurred (such as the user clicking a button, or the response from an AJAX request is received). So imagine we had a Cat and we wanted it to meow if we pressed a button, e.g:

// cat.js
class Cat 
{

    constructor(n,a)
    {
        this.theName=n;
        this.age=a;
    }

    meow()
    {
        alert(`${this.theName} says meow!`);
    }
}



// Create the cat
const tom = new Cat("Tom", 7);

// When the user clicks the button, the meow method will run
document.getElementById("meowbtn").addEventListener("click", tom.meow);

HTML:
<html>
<head>
<script type='text/javascript' src='cat.js'></script>
</head>
<body>
<input type='button' id='meowbtn' value='Meow!' />
</body>
</html>

If you try this out, and click the button, does the expected output:

Tom says Meow!
occur? No. Instead, you will get something like:
undefined says Meow!
Why is this happening?

Dealing with "this" issues in callbacks with bind()

Luckily, the issue is easy to deal with. You can simply use the bind() method. bind() does what it says on the tin: it binds the callback function to the specified object, so that when the callback is called, this will refer to the correct object. So to rewrite our Cat example so they work correctly:

class Cat 
{

    constructor(n,a)
    {
        this.theName=n;
        this.age=a;
    }

    meow()
    {
        alert(`${this.theName} says meow!`);
    }
}


// Runs when the page loads

// Create the cat
const tom = new Cat("Tom", 7);

// When the user clicks the button, the meow method will run
    document.getElementById("meowbtn").addEventListener("click", tom.meow.bind(tom));

Note how the callback tom.meow() is now bound to the object tom. This means that this will refer to the correct object, tom, when tom.meow() is called as a callback on response to a button click, or indeed any other event.

bind() and arguments

You can specify arguments to be sent to a bound function. For example, imagine we wanted to make the cat walk a certain distance when the user clicks a button. We need to make the walk() method a callback (and therefore, use bind()) but we also need to pass in the distance to walk. How can we do this?

class Cat 
{

    constructor(n,a,w)
    {
        this.theName=n;
        this.age=a;
        this.weight=w;
    }

    meow()
    {
        alert(`${this.theName} says meow!`);
    }

    walk(d) 
    {
        this.weight -= d;
    }    
}


// Create the cat
const tom = new Cat("Tom", 7, 50);

// When the user clicks the button, the walk() method will run
// and will take an argument of 5
document.getElementById("walkbtn").addEventListener("click", tom.walk.bind(tom, 5));

Note here that when we call the walk() function as a callback whenever the user clicks the 'walk' button, walk() automatically receives an argument of 5; the parameter d will be set equal to 5, so the cat will walk 5 units of distance. The rule is that arguments to bind() following the context of "this" will become parameters to the specified callback function.

Exercises

Write these using CommonJS modules. Use one module per class and write an index.js file to act as the entry point for your application, which requires the module(s) you need. See here.

Revisit your car scenario from Topics 8 and 9. Ensure you write your Car as a class. Add additional methods to start and stop the engine (similar to the example above) and ensure that accelerate() will only run if the engine is running, otherwise display an error. Add buttons on your page to start and stop the engine and accelerate and decelerate the car, and to display the current car details. Connect these buttons to the appropriate methods of Car, ensuring that you use bind() to preserve the context of this.

  • This will require you to read up on key events, which is in the further notes for Topic 10 (see here). Create a class representing a Hero in a game. It should have x and y coordinates, and also an Image object, as properties. Give it a display() method. The display() method should take a canvas context as a parameter and, using the canvas context, should draw the Hero at its current x and y coordinates. Also give the Hero methods to move left, right, up and down, which should change x and y appropriately. Connect this to a web page with a canvas, and using key events, move and display the hero in response to the arrow keys by calling the appopriate methods of the Hero.