A Lesson on JavaScript Objects and Prototype’s bindAsEventListener Method

This post covers 3 topics about JavaScript:

  • How objects work in JavaScript and what makes them useful.
  • A concise explanation as to why the this variable is confusing at times.
  • A clear explanation of bindAsEventListener since the official one is confusing at best.

An Overview of JavaScript Objects

To dumb things down, in JavaScript, functions can be assigned to variables. Thus, you can do cool stuff like this:

var eat = function() {
    alert(‘munch, munch’);
};
// displays ‘munch, munch’
eat();

Objects are denoted by using curly brackets. Inside an object, you can put variables. The syntax is a little different, so pay attention. Thus:

var Michi = {
    favoriteFruit: ‘banana’,
    favoriteBread: ‘whole grain’
};
// displays ‘banana’
alert(Michi.favoriteFruit);

Note: I don’t like either of those that much.

The colon is sort of an equals sign. It essentially equates to “variable name: value”. Each assignment is separated by a comma and the “value” portion can be anything that you can assign to a regular variable. Since you can assign variables inside objects, that means you can put functions inside objects. Then you can call a function by putting a pair of parentheses () on the end of the variable. Again, pay attention to the syntax:

var Michi = {
    eatFruit: function() {
        return ‘Eating: banana’;
    }
}
// displays ‘Eating: banana’
alert(Michi.eatFruit());

In a truly object oriented example, calling eatFruit() should cause Michi to eat his favoriteFruit variable:

// does the same thing as the previous example
var Michi = {
    favoriteFruit: ‘banana’,
    eatFruit: function() {
        return ‘Eating: ‘ + this.favoriteFruit;
    }
}

This is where stuff gets sticky. Or maybe gooey. Nah, I won’t make a pun here. this refers to Michi. This is important because later we might copy Michi into another variable Tom. When that happens, this now refers to Tom‘s favorite fruit, which might be an orange. this is a dynamic placeholder for whatever object that we are inside of.

How *this* Complicates Things

Let’s continue illustrating the problem by building another example:

function watch(personEating) {
    alert(personEating);
}
// displays “Eating: banana”
watch(Michi.eatFruit());

This will do exactly what you think it will do. It will cause Michi to eatFruit(). However, the next example is where things get very tricky:

function watch(personEating) {
    alert(personEating());
}
// the goal is to display “Eating: banana”
watch(Michi.eatFruit);

This will fail. Why? Notice the very subtle differences between the two examples. Look at section I highlighted. In the first example, the function is called and its result, the text “Eating: banana” is then sent to alert(). In the second example, the function Michi.eatFruit is passed in as a variable. It is then called as a function from inside watch(). Translated, that code renders like this:

function watch(personEating) {
    // this temp stuff isn’t necessary, but it should make
    // things easier to understand

    var tempFunction = function() {
        return ‘Eating: ‘ + this.favoriteFruit;
    }
    var tempText = tempFunction();
    alert(tempText);
}

As you can see, the problem is due to the this.favoriteFruit variable. this now refers to the function watch()‘s parent (e.g., nothing at all), which doesn’t have a favoriteFruit variable. this no longer refers to Michi. This is a very common and very frustrating part about JavaScript. To get around this, the popular prototype library has a fix.

Prototype’s bindAsEventListener

The official documentation (as of this writing) for this method is dead wrong. Their example is wrong. Their explanation is fuzzy. Maybe somebody over there will read this and fix it up. Prototype’s example doesn’t even work! The corrected example is:

var obj = { name: ‘A nice demo’ ,
    handler: function (e) {
        var tag = Event.element(e).tagName.toLowerCase();
        var data = $A(arguments);
        data.shift();
        alert(this.name + ‘\nClick on a ‘ + tag + ‘\nOther args: ‘ + data.join(‘, ‘));
    }
};
Event.observe(window, ‘click’, obj.handler.bindAsEventListener(obj, 1, 2, 3));

So I will try to explain how to use it correctly. The syntax is simple:

parentObject.someFunction.bindAsEventListener(parentObject);

Thus, in my previous example that broke, you would rewrite it as follows:

function watch(personEating) {
    alert(personEating());
}
// was watch(Michi.eatFruit);
watch(Michi.eatFruit.bindAsEventListener(Michi));

When the code is executing, it knows that this refers to Michi, thanks to that argument being passed in. The argument represents what this refers to. So you would still call the method exactly as you normally would, but you tack bindAsEventListener on the end.

I hope this was educational. The fixed demo code will run for certain since I tested it; the rest: I am not as sure (95%).

REMEMBER: Word Press likes to convert my quotation marks into the slanted ones. So if you cut and paste my code, make sure you change those!

Comments are closed.