JavaScript 'this' is Not Always What You'd Think: Use Closures

Posted on 30 July 2008 by Johannes Fahrenkrug. Tags: JavaScript Programming OpenSocial
JavaScript has quite a few design flaws (see the chapters "Awful Parts" and "Bad Parts" in Douglas Crockford's great book "JavaScript: The Good Parts"). One bad part to look out for is described in detail here. To sum it up: "this" is not always bound to what you'd think. Take this example:

var myObj = {
value_a : 'Soylent Green',
value_b : 'is People',
printA: function() {
alert(this.value_a);
}
}

//if you call the method directly on the object, "this" is bound to the object as expected:
myObj.printA(); // --> "Soylent Green"

//if you don't invoke it on the object, "this" is bound to the JavaScript global object. This is a very bad part
var print_method = myObj.printA;
print_method(); // --> undefined

This can cause lots of wailing and gnashing of teeth. I ran into this in an OpenSocial application. OpenSocial uses lots of callbacks which take functions as parameters. In that case, exactly the problem occurs that the above code demonstrates.
What can we do? We can use one of JavaScript's beautiful features: Closures. I'll just show you a piece of code that demonstrates what they do:

var closure = function() {
var a_value_that_lives_on_after_this_method_finishes = 'I am alive!';

return function() {
//this function will be returned but it can still access that long variable name up there
alert(a_value_that_lives_on_after_this_method_finishes);
};
}(); //notice that we create the anonymous function and invoke it right away.

//we can now call the returned function and behold! it can access that long variable name!
closure();

Great! We can use that (no pun intended, read on...) to solve our "this" problem. Look at this code. It it a super simple OpenSocial application. You can run it in your container when you point it to this URL: http://opensocial-friendscolor.googlecode.com/svn/trunk/thistest/thistest.xml
Here's the code:

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
title="JavaScript this Test"
height="600">
<Require feature="opensocial-0.7"/>
</ModulePrefs>


<Content type="html">
<![CDATA[
<script>
// we are creating and calling a function that returns an object literal.
// before returning it, we assign it to the variable "that".
// JavaScript closures enable us to still reference "that" even after the function ended.
// That way we always have a way to access our object reliably.
var thistest = function() {
var that = {
string_one : 'Soylent Green',
string_two : 'is People!!!',
printOne : function() {
console.log('Printing string one: ' + that.string_one);
},

printTwo : function() {
console.log('Printing string two: ' + that.string_two);
},

printBoth : function() {
that.printOne();
that.printTwo();
},

getViewer : function() {
var req = opensocial.newDataRequest();
var params = {};

params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS]=[opensocial.Person.Field.GENDER, opensocial.Person.Field.PROFILE_URL];

req.add(req.newFetchPersonRequest("VIEWER", params), "viewer");

req.send(that.getViewerCallback);
},

getViewerCallback: function() {
//I don't care about the viewer data, just about what value "this" has
console.log('in getViewerCallback');
console.log('this:');
console.log(this);
console.log('that:');
console.log(that);
that.printBoth();
},

runTest : function() {
console.log('in runTest');
console.log('this:');
console.log(this);
console.log('that:');
console.log(that);
that.printBoth();
that.getViewer();
},

runOnLoadTest : function() {
console.log('in runOnLoadTest');
console.log('this:');
console.log(this);
console.log('that:');
console.log(that);
that.printBoth();
that.getViewer();
}
};

return that;
}(); //notice that the function is invoked right away

gadgets.util.registerOnLoadHandler(thistest.runOnLoadTest);
</script>

<h2>JavaScript this Test</h2>
<p>This demo shows that the JavaScript 'this' object is not bound to what you'd expect if a method is called with registerOnLoadHandler, except if you use a closure. Watch your console.</p>


<input type="button" value="Run Test directly, without the onLoadHandler" onclick="thistest.runTest();"/>
<p>(2008, <a href="http://blog.springenwerk.com">Johannes Fahrenkrug</a>)</p>
]]>
</Content>
</Module>


If you run this within Orkut, this will be show up in your Firebug console:


You can see that "this" doesn't have the value we'd want it to have: our thistest object. But by using a closure and assigning our object literal to the variable "that" we can solve the problem. Now we can always access our thistest object by saying "that" instead of "this".

Comments

Please keep it clean, everybody. Comments with profanity will be deleted.

blog comments powered by Disqus