Showing posts with label this. Show all posts
Showing posts with label this. Show all posts

Tuesday, 1 April 2008

You must remember 'this'

Of all the tech blogs in all the sites in all the worldwide web, you walk into mine...

If you hang out in JavaScript-oriented newsgroups like these for any length of time, you will eventually see some variation of this question:

Hey, why doesn't this work?
function MyWidget(name)
{
this.name = name;
this.element = null;
}
MyWidget.prototype.showName = function()
{
alert('The name is ' + this.name);
}
MyWidget.prototype.hookElement = function(element)
{
this.element = element;
Event.observe(this.element, 'click', this.showName);
}
function test()
{
var widget;

widget = new MyWidget('Test Name');
widget.hookElement(document.getElementById('testDiv'));
}
"testDiv" is a div in the document, and I know that I'm not calling the test() function before the DOM is loaded, so why is it when I click the div I get the message "The name is undefined"?!
(As always, I'm using some convenience syntax in the above for hooking up the event handler.)

The OP (original poster) might even follow on with:
I even tried changing the observe() line to this:
Event.observe(this.element, 'click', function() {
this.showName();
});
because I heard somewhere that you have to do that, but that's even worse, it causes an error saying this.showName() isn't a function?!
The issue here is that the OP hasn't quite grokked "this" and its special role in the JavaScript world.

I talked a bit about 'this' over here, but I wanted to do a post focussing on the specific pitfall the OP above, like so many of us, has fallen into (forgetting 'this') and how you deal with it.

Let's look at what's wrong with this line first:
Event.observe(this.element, 'click', this.showName); // Wrong
JavaScript doesn't have methods (see link above), and so this.showName just returns a function reference with absolutely no connection to the instance the OP wanted to bind to the element. It's just a function. (Used properly, this is a powerful feature, but in this situation it's causing the OP some trouble.) Recall that showName is defined like this:
MyWidget.prototype.showName = function()
{
alert('The name is ' + this.name);
}
Within the code, 'this' is determined not by where the event handler is set up, but by how the function gets called. Most likely, 'this' will be a reference to the element that was clicked ('testDiv'), because modern browsers use the element related to the event as 'this' within event handlers. Consequently, this.name is undefined unless the element in question happens to have a name attribute.

So to get the intended effect, you have to wrap the call to this.showName() so that 'this' is the MyWidget instance when the code gets executed -- you must remember 'this'. Which is probably what the OP heard about when he tried this:
Event.observe(this.element, 'click', function() {
this.showName();
}); // Still wrong
This is getting closer, and in fact it would work if we were using a variable to reference the widget rather than 'this', but because it's 'this', we actually still have exactly the same problem we had before: When the event handler gets called, 'this' is the element, not the widget, and so there's no showName() function to call.

So how do we deal with this? Well, here's one approach I've seen to rewriting the hookElement function:
MyWidget.prototype.hookElement = function(element)
{
var self;

this.element = element;
self = this;
Event.observe(this.element, 'click', function() {
self.showName();
});
}
This works because we're no longer using 'this' within the event handler, we're using 'self' (the event handler has access to 'self' because it's a closure; more here). So that solution works. I can't say I like it, though. It just feels...hacky, I guess. But still, it works, and although it looks a bit funny the first time, if you're familiar with the idiom you read right past it thereafter. You just need to be sure the closure isn't unnecessarily preserving some other big amount of data from elsewhere in the function.

Personally, though, I prefer using a reusable "binding" function. Many JavaScript toolkits have these (such as Prototype's bind() and bindAsEventListener()), but it's not complicated:
function bind(f, obj)
{
return function() {
return f.apply(obj, arguments);
};
}
This is a function factory: It creates functions that, when called, will call the given function with the given object set as 'this' (using JavaScript's convenient apply() function; insert your own "The fundamental things apply" joke here). Now we can rewrite the OP's hookElement function like so (changes from the original at the top in bold):
MyWidget.prototype.hookElement = function(element)
{
this.element = element;
Event.observe(this.element, 'click',
bind(this.showName, this)
);
}
You might be wondering why we have to specify 'this' twice. Remember that this.showName just returns a function reference, with nothing about the instance (we could replace this.showName in the above with MyWidget.prototype.showName if we liked). If we want bind() to know what instance we want to bind the function to, we have to specify it -- the this at the end.

And that's it! Now the event handler works as the OP expected it to.

Monday, 24 March 2008

Mythical methods

Note: As of ES2015, JavaScript arguably got methods (and that is the term the spec uses) via a new notation on object initializers and via the class syntax. This article predates ES2015 and refers to non-method properties on prototypes, created with the function keyword (the new method syntax doesn't use the function keyword).
We frequently talk about JavaScript objects having methods. This is just a convenient myth. JavaScript has functions, but it doesn't have methods. It doesn't need them. Its functions, combined with some syntactic sugar, are more than up to the job.

What is a "method"? I'd have to say that the definition given on Wikipedia is pretty good. As of this writing, it says a method is
...a subroutine that is exclusively associated either with a class...or with an object...
Yeah, JavaScript doesn't have any of those. Granted it seems to have them. For example:
function Guess(killer, location, weapon)
{
    this.killer = killer;
    this.location = location;
    this.weapon = weapon;
}
Guess.prototype.accuse = function()
{
    alert('It was ' + this.killer +
          ' in the ' + this.location +
          ' with the ' + this.weapon +
          '!');
};

function testGuess()
{
    var mustardStudyLeadPipe;
    var plumHallRope;

    mustardStudyLeadPipe = new Guess(
        'Colonel Mustard',
        'study',
        'lead pipe');
    mustardStudyLeadPipe.accuse();

    plumHallRope = new Guess(
        'Professor Plum',
        'hall',
        'rope');
    plumHallRope.accuse();
}
Running testGuess does indeed show us
"It was Colonel Mustard in study with the lead pipe!"
and then
"It was Professor Plum in hall with the rope!"
so that looks an awful lot like accuse is a method of Guess objects.

Except it isn't. Let's add a bit to our testGuess function (new bits in bold):
function testGuess()
{
    var mustardStudyLeadPipe;
    var plumHallRope;
    var accuse;

    mustardStudyLeadPipe = new Guess(
        'Colonel Mustard',
        'study',
        'lead pipe');
    mustardStudyLeadPipe.accuse();

    plumHallRope = new Guess(
        'Professor Plum',
        'hall',
        'rope');
    plumHallRope.accuse();

    accuse = mustardStudyLeadPipe.accuse;
    accuse();
}
What does the final call to accuse show? Does it accuse Colonel Mustard? No, all we've done is get a reference to the accuse function into our local variable, there's nothing there (or in the function definition) that refers to the mustardStudyLeadPipe instance or indeed anything related to Guess. No, what this shows will depend on the HTML document in which it's found, but it'll be something along these lines:
"It was undefined in the http://blog.niftysnippets.org with the undefined!"
Why? Because we didn't do anything to define "this" within the function. (I'll come back to details on why we'd get seemingly-odd alert that includes a URL in a bit.)

There are three main things about JavaScript that make it seem to have methods (leaving aside prototypes and "classes" for the moment): The "this" keyword, the fact that object properties can refer to functions (since they are, after all, just objects), and the fact that when you call a function using an expression that gets the function reference from an object property (e.g., object.functionName() or object['functionName']()), the object is set automatically as "this" within the function call.

Let's look at each of those.

The "this" keyword: This keyword looks familiar if you're coming to JavaScript from a background in C++, Java, C#, and the like, where "this" within a method is guaranteed to refer to an instance of the class defining the method (or a subclass). And in JavaScript, "this" does refer to an object instance, but that's where the similarity ends. Other than the name being the same and it referencing an object, it bears no relation to the "this" keyword in class-based languages like C++, Java, or C#. In fact, in many ways, "this" is actually just a function argument that is supplied in a non-obvious (but convenient!) way.

Functions as properties: Let's look again at one line from the code above:
mustardStudyLeadPipe.accuse();
If we were talking about that line of code, we'd probably say "...the accuse method of the mustardStudyLeadPipe object...", which is a useful and convenient way to put it. A more painstakingly-geeky-accurate way of putting it, though, would be "...the function referenced by the accuse property of the mustardStudyLeadPipe object..." Not that anyone's going to say that, but that's really what's going on. Objects don't have methods, they have properties; it's just that a property can refer to a function, since functions are objects like everything else.

Functions called via property references get "this" set for them: This is the part that really makes it seem like JavaScript has methods: The "this" reference gets set automagically to the object instance if you call a function via a property reference. Let's look at that call again:
mustardStudyLeadPipe.accuse();
This does three completely distinct things: Firstly, it identifies a function to call by getting the function reference from an object property; secondly, it says to call the function and return its result rather than just get a reference to it (the parentheses do that); and thirdly, it says that within that call, use the object in question as "this". These completely distinct things are combined in that notation for our convenience. Getting the function reference from an object property doesn't link it in any way to the object the property came from (as our accuse test above confirmed), it just gets a reference the function; the JavaScript engine treats calls of functions just retrieved from object properties as special and sets up "this" accordingly, but it has nothing to do with the function being called.

Lets prove that another way:
function testGuess2()
{
    var plumHallRope;
    var fakeGuess;

    plumHallRope = new Guess(
        'Professor Plum',
        'hall',
        'rope');
    plumHallRope.accuse();

    fakeGuess = {};
    fakeGuess.location = 'library';
    fakeGuess.killer = 'Mrs. Peacock';
    fakeGuess.weapon = 'revolver';
    fakeGuess.demo = plumHallRope.accuse;
    fakeGuess.demo();
}
This accuses Professor Plum as before, and then:
"It was Mrs. Peacock in the library with the revolver!"
The exact same function produces the alerts in both cases, it's purely the way it was called that defined "this". The fakeGuess object isn't even really a Guess -- "fakeGuess instanceof Guess" returns false -- but it has all of the properties the function expects (killer, location, weapon), and so it works just fine (this is something we'll come back to in a later post). Essentially, "this" is just a function argument that's passed into the function as an implicit feature of calling a function via a property reference.

We can also do it explicitly. JavaScript gives us the call and apply methods on function instances, with which we can say explicitly what we want "this" to be. (They do the same thing, they just provide different ways to specify the function's arguments.) If you say myfunction.call(myobject), you're saying "call myfunction and use myobject as 'this'", which is what the property-retrieval stuff does implicitly for you. In fact, this:
plumHallRope.accuse();
equates to
plumHallRope.accuse.call(plumHallRope);
Now all three of the parts we identified above are shown distinctly: Getting the function reference from the property (plumHallRope.accuse) is distinct from the fact we're calling it (call) is distinct from what "this" should be ((plumHallRope)). The fact that these are distinct can be made clearer:
var f;
f = plumHallRope.accuse;
f.call(plumHallRope);
Alternately, here's a more dramatic example:
function testGuess3()
{
    var plumHallRope;
    var fakeGuess;

    plumHallRope = new Guess(
        'Professor Plum',
        'hall',
        'rope');
    plumHallRope.accuse();

    fakeGuess = {};
    fakeGuess.location = 'library';
    fakeGuess.killer = 'Mrs. Peacock';
    fakeGuess.weapon = 'revolver';

    plumHallRope.accuse.call(fakeGuess);
}
Not to bang on about it, but the call at the end accuses Mrs. Peacock, not Professor Plum. The plumHallRope instance was only used to get the function reference, it wasn't used within the function call. In our original testGuess function, we could even do this if we wanted to:
mustardStudyLeadPipe.accuse.call(plumHallRope);
Which accuses Professor Plum. But it's more convenient, isn't it, to say plumHallRope.accuse?

So if "this" is really just a sort of obscure function argument, why have it? Why not just write everything as global functions and pass in the object to act on as the first argument? We could do that (and in fact I did it for years, as a C programmer), but it's more cumbersome. You have to know what functions are meant to be used with what kinds of objects, there's all sorts of stuff littering up the global namespace, etc., etc. By passing around object references, which have properties on them (usually inherited from their prototype; again the topic of an upcoming post) that reference functions intended for use with them, it's just so much more convenient.

Okay, but what was that about that URL showing up earlier when we called accuse? You'll remember earlier when we ran this code:
    accuse = mustardStudyLeadPipe.accuse;
    accuse();
we got the alert with the URL in it. So why did that happen, and where did the URL come from? It was because we didn't give an object to use for "this", and so the function call got the default, which is the JavaScript global object. In browser implementations, the global object is the window object -- so we were showing the values of window.killer, window.location, and window.weapon. In a typical situation, window.killer and window.weapon will be undefined, and of course window.location is the URL of the document in the window.

I chose that example intentionally, because it's a pitfall people fall into a lot: Losing "this". Usually, people lose "this" in the context of an event handler -- e.g., they have an instance (plumHallRope, perhaps), and want to hook up a "method" on it (say, accuse) to an event (a click handler for a button, maybe?), and so understandably they do something like this (I'm again using convenience syntax, as I describe here):
Event.observe('theButton', plumHallRope.accuse); // WRONG
The problem being, that just references the function, nothing about its context. Its context will be determined by how it's called. Earlier when we called accuse directly, because we didn't do that via property retrieval or call or apply, "this" was the global object; event handlers like this one will usually get "this" set to the element (although not if you're using old DOM0 -- onclick attribute -- events or IE's attachEvent function). To maintain its context, you have to do something like this:
Event.observe('theButton', function() { plumHallRope.accuse(); });
The function that gets called by the event handler then turns around and calls the accuse function such that plumHallRope is "this", so you've preserved "this" using a wrapper (which is also a closure; details). (Note that thanks to a bug in Internet Explorer, that may well cause a memory leak for your IE users. Prototype's Event.observe() does some things to minimize the issue; other frameworks will have other helpers along those lines.)

In Conclusion:

I've seen people deride JavaScript for having "fake" methods, but I think that's missing the point. It doesn't have fake methods, it just doesn't have methods at all. What it does have is very powerful, flexible functions and some convenient syntactic sugar that lets us express the 90% case -- calling a function related to an object instance passing in that object instance as an argument -- in a compact and expressive way, but without limiting our use of the functions involved. By not limiting our use, we can use just about anything as a mixin, we can use duck typing, easily create wrapper objects to enforce or verify contracts, etc.

Like so many things about the language, it's confusing until you grok just how simple it is, and then you start appreciating that it's simple but powerful.