Overcoming Scope Limitations in jQuery AJAX Callbacks
Update: Not long after writing this post, I realized that $.ajax() has a configuration option named context, which allows you to assign scope to a callback method. This knowledge renders the specifics of what I say below pretty much irrelevent, but closures remain a very slick solution to scoping problems in other situations.
If learning jQuery is teaching me anything, it’s the power of closures. Recently I’ve been working on a jQuery-powered gallery for a client. My code dynamically retrieves JSON data from the server. The problem is that such a construct as the one below doesn’t work:
Gallery = function(id) { this.id = id; this.loadGallery = function() { $.ajax({ 'url' : '/gallery.php', 'data' : { 'gallery_id' : this.id }, 'dataType' : 'json', 'success' : this.populate }); }; this.populate = function(data) { // populate() gets called, but not in the scope of the Gallery object. // So the following call will output "undefined". alert(this.id); }; }; |
Unfortunately jQuery doesn’t provide us with a mechanism to set the scope of our callbacks like some frameworks but fortunately for us JavaScript provides a way around that: the closure.
Gallery = function(id) { // ... this.loadGallery = function() { $.ajax({ 'url' : '/gallery.php', 'data' : { 'gallery_id' : this.id }, 'dataType' : 'json', 'success' : function(gallery) { return function(data) { gallery.populate(data) }; }(this) }); }; // ... }; |
What the heck does this all mean? In JavaScript it’s completely legal to create an anonymous function (also known as a closure) and call it, all in a single statement. So:
function(gallery){ ... }(this) |
creates an anonymous function and immediately calls it, passing this, which is a reference to the Gallery instance on which the loadGallery is being called, as the first parameter to the function. What are we doing inside that function?
return function(data) { gallery.populate(data) }; |
Remember: in JavaScript everything is an Object including functions, so it’s completely legal for one function to return a reference to another function or, in this case, an anonymous function .
The closure being returned has a single argument named data, and because it is defined within the scope of the outer closure, we can gall methods on gallery, such as populate(). It’s a long way to go to resolve the scoping issue, but it does work.