dojo.hitch and scope - more romance

Scope in javascript is one of those topics that won’t go away. It continues to trip people up again and again, despite numerous articles including a great cookie right here on dojo campus. In this cookie, I’m going to sneak up on dojo.hitch from a different angle by re-visiting some of the basics of how function scope works.

First, let’s recap on scope: In javascript, there is only function scope. If you come from just about any other language, this is a lesson you need to engrave on your forehead in bright purple ink. Observe:

function doLoop(arr) {
  for(var i=0; i<arr.length; i++) {
    var item=arr[i];
  }
  alert("i: " + i);
  alert("item: " + item);
}
function init() {
  doLoop(["some", "stuff"]);
  alert("outside the function: " + item)
}

..Even though var item is declared within the for block, it remains in scope outside that block and shows up in the alerts after the for loop. var i is also in scope throughout the function, however, they are out of scope outside the function.

Now let’s recap on function execution scope. If you were looking the other way during the doldrum years that surrounded the release of Javascript 1.5, you might have missed 2 new features: Function.apply and Function.call. They let you invoke a function in a given scope and pass in arguments in different ways. They are a big part of what makes modern javascript practical; they let you specify what this should be when the function is invoked. We’ll use dojo.declare for this example, but there’s no dojo magic going on here.

dojo.declare("Person", null, {
  constructor: function(name, petName) {
    this.name = name;
    this.getPrivateName = function() {
      return petName;
    }
  },
  getName: function() {
    return this.name;
  }, 
  getLocation: function() {
    return location; 
  }
});

We create an object - a “class” instance:

person1 = new Person("Mr. First Person", "cuddles");

Notice that when we use the this keyword inside our class methods, it will always refer to the object that new Person returned; the instance. We’ve defined a standard, somewhat redundant, getter for the name property, and we’ve got a getter for the private petName variable, courtesy of a closure - petName is in scope at the time the function is defined, so it “closes-over” that variable, giving us a way to access a scope that is otherwise hidden from us. I’ll come back to the getLocation method.

Now, watch closely. person1.getName is a function reference. It is the function which is a property of our Person instance. If I call it: person1.getName() it returns me the name property as expected. But what happens if I store a reference to that function (method) in a variable?

var nameGetter = person1.getName; 
alert(nameGetter());

.. Nothing. Actually you might get something - its going to look for the name property of this, which in this case is the window - the global object and default scope. We lost context when we stored that function reference. To get it back, we can use the call method that all function objects inherit from Function:

alert( nameGetter.call(person1) );

That’s more like it. So, finally, what does dojo.hitch do, and how does it help? dojo.hitch is a convenience function, that makes it super-easy to make the kind of closures we’ve seen here, and get back a function reference that when invoked will have the exact context and scope you wanted. That last rather trivial example could be re-written:

var nameGetter = dojo.hitch(person1, person1.getName); 
alert(nameGetter());

Paraphrased: give me a function that’s going to call the person1.getName function, where “this” is person1.

getLocation: function() {
    return location;
  }

It returns the location, in the scope as-was when the function was defined. For that we have to look to the context surrounding the function definition. We have the object (the property bag that will be fed to dojo.declare to populate our instance), and up from that is only our global object- the window (see this explanation on the scope-chain for more on this.) So as we define getLocation, we close around the window.location object. Its value might change (actually it probably wont as its the location) but the object to which it points will not. We can explore this more:

function init() {
 var location = "In the init function"; 
 alert( person1.getLocation() );
 newLocationGetter = dojo.hitch(person1, function() {
  return this.name + " is: " + location; 
  });
}
init();
alert( newLocationGetter() );

As we walk though the init function, there’s 2 location variables: the window.location object, and this new locally scoped location string. person1.getLocation() still gives back the window.location object though. If we want to take this context with us and bundle it up into a new getLocation method, we can do that. The anonymous function by itself would close around the string location, but wouldn’t have the right “this“. dojo.hitch gives us that - we get a portable function defined in the right context, that will run with the correct scope.

Some things take a while to sink in. This whole scope/function thing is definately one of them judging from the volume of questions we get on #dojo, the mailing list and forums. This little cookie is hardly the last word on the topic, but hopefully I’ve answered more questions than I’ve raised, and got you thinking about scope as it applies to your code. And while you’re in that frame of mind - now would be a great time to re-read Jammermaster Goat - dojo.hitch.

Note: Astute readers pointed out I was promoting further misunderstanding with this article as first published. I’ve re-jiggered it to correct the mistake, thanks Seth.

Also note: Doing discovery and debugging with alert is of course for losers. But I didnt want to use console.log and then face the inevitable “I get console is not defined” comments here.

Tags: