Posted in Dojo Cookies
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: dojo.hitch