Friday, August 31, 2007

Javascript scoping and this

Javascript has been a surprising language, mostly because the difference between my experience with it now, and when I touched it in high school. Going along with the theme of seeing everything as the proverbial functional nail, there's been a bunch of things to get use to, such as how scoping work works in javascript.

Javascript is a lexically scoped language (scope determined when function is defined, not when it's executed). This much, I think I get.

However, unlike Java and C++, "instance methods" of Objects doesn't include attributes in its scope. One has to explicitly refer to it. So let's say I have something like the code below. Just something simple, where one wants to process each element in a list. Of course, one can do this with a for loop and it wouldn't be an issue, but since playing with Ruby, I've found that iterators were more clear when writing code. It's probably bad design, but for the sake of argument, let's say that tile's draw() method takes the Renderer object as an argument.

function Renderer() {
this.tiles = new Array();
}
Renderer.prototype = {
draw: function() {
this.tiles.each(function(tile) {
tile.draw(this);
});
}
};

This will not work, because apparently, "this" inside the anonymous function of each() doesn't refer to renderer, but (I think) to the anonymous function. So draw() will usually complain something about how the renderer doesn't have the right properties.

This is a bit odd, since this would work in Ruby. The only way I've found around this is just to throw "this" into a variable, then refer to that variable.

function Renderer() {
this.tiles = new Array();
}
Renderer.prototype = {
draw: function() {
var my_renderer = this;
this.tiles.each(function(tile) {
tile.draw(my_renderer);
});
}
};

According to javascript's scoping rules, this works. But to me, it looks rather ugly. Anyone know of an easier way to get around it?

2 comments:

  1. Hey, I ran up against this problem a lot too. The keyword "this" inside a function that isn't bound to an object will refer to the window object, which is what "this" refers to by default. Why does "this" do this? Because functions in JS are "first-class objects". That's another term to add to "lexically scoped". :-)

    There *is* a way around creating a variable to refer to what "this" is naming in your draw function. Use the anonymous function's bind method.

    Yes, functions have methods in JS. Here's how you'd do it:

    draw: function() {
      this.tiles.each(function(tile)   {
        tile.draw(this);
      }.bind(this));
    }

    I'm still not comfortable enough with how this works to try to explain it precisely. But here's how I think of it: the anonymous function is now "bound" to the object named by "this" inside the scope in which the function is defined. In this case, that's the draw function. And in the draw function, "this" names a new Renderer instance. So now, "this" inside the anonymous function will name the Renderer instance too.

    BTW, you can pass in any object, I think, with bind, and that object becomes "this" inside the function in question.

    Related to bind is apply, which is also a function method. myfunction.apply(some_obj, args) invokes the function as though it were a method of some_obj. It's equivalent to some_obj.myfunction(args).

    I highly recommend David Flanagan's Javascript book, from O'Reilly. It's the one with the rhino on the cover. I use it all the time, and it always answers my JS questions.

    Finally, I applaud your interest in Javascript. I think it's a great and surprising little language that everyone still thinks is only good for image rollovers. I'm enjoying reading your posts. Keep investigating!

    ReplyDelete
  2. Thanks for the tip. I had always skipped over sections on bind in any language because I never had to wrestle with scope much. It kinda did what I expected. I tried it out, and it does work--with anon functions. I'm guessing one can use apply with named functions. However, it seems to break all sense of encapsulation.

    I have the Rhino book, and it's not bad so far.

    Thanks. I usually try to post something I learned or that I found interesting in hopes that other people will also.

    ReplyDelete