Consider the following jQuery implementation defined using an object literal…
$(function() {
var myObject = {
methodOne: function()
{
$('#element').animate(
{'marginLeft': '50px'},
'slow',
function() {
myObject.methodTwo();
}
);
},
methodTwo: function()
{
$('#element').animate(
{'marginLeft': '-50px'},
'slow',
function() {
myObject.methodOne();
}
);
}
} // End myObject
myObject.methodOne(); // Execute
});
For the record, the above code works just as expected. What I don’t understand is why a subtle and seemingly harmless change like the following…
methodOne: function()
{
$('#element').animate(
{'marginLeft': '50px'},
'slow',
myObject.methodTwo() // No more anonymous function
);
},
… to both methodOne and methodTwo causes a browser error stating too much recursion. What’s the difference between how I’ve declared my callback? Also, if I bring back the anonymous function declaration, but modify the object reference to look like this…
methodOne: function()
{
$('#element').animate(
{'marginLeft': '50px'},
'slow',
function() {
this.methodTwo(); // assuming 'this' refers to 'myObject'
}
);
},
… I get one good pass through methodOne and upon callback my browser freaks out because it cannot find methodTwo. My guess is that I fell out of scope somewhere, but I can’t rightly decide where. Your insight is much appreciated!
Lets break this down
In this example you pass a function object as a callback. That function object is called when animation is done. It has the
myObjectobject shared via closure, so it can easily find it and call a method on it. Awesome!Here something different is going on. As the callback here you are actually passing the return value of
myObject.methodTwo(), and not the actual function object. So sincemethodTwo()doesn’t return anything, thenundefinedis actually passed as your callback. Meaning that theanimate()function thinks there is no callback.So maybe you meant to try this!
Well this still wouldn’t work. Now you are passing a function object for the callback, yes, but it will lose context (
this). It turns out that when you invoke a function object on it’s own, thenthisis the global object. Check this out:So you cant pass a function object directly in for a callback that is supposed to be invoked like a method with an object as the receiver. Internally to the
animate()method there, it executescallback()for you, which is a call that does not preserve the value ofthisfor you at all.So why didnt this one work?
When an anonymous function is invoke as the callback, just like breaking off a method,
thisdefaults to the window object. So this code actually callswindow.methodTwo(), which doesn’t exist, and it explodes.So the accepted standard JS way to do this your first way.
That should always work, even though it seems wasted because you invoking 2 function to do one thing. But as you are discovering it’s the least error prone.
Learning how
thisworks in JS is a painful experience, but when you get it, you find that the rules are pretty straight forward and easy to manipulate.If you still dont like that, you can do some tricky js magic. Like underscore.js bind() method.
This returns a function that will always run with
someObjasthiswhich can safely be used as a callback.Or if you want to try out CoffeeScript it has a fat arrow to preserve context, wich compiles to JS similar to what the underscore.js
bind()method does.In this case, the
=>style of function declaration preserves the context of the scope where it appears, so this can be safely used.