Solving the JavaScript scoping madness in a smart way

JavaScript is a really cool language, I like it, but it's scoping issues can be a real pain in the ass. In this little post I will explain what the problem is and how I solved it (this was solved before, but I think my solution is beautiful and very simple). In the end of this post I will post my code... served for your use!

The problem

We start with this simple JavaScript code:

var MooCow = function() {
  this.cow_name = "SheLikesMilk";
}
MooCow.prototype.alertCowName = function() {
  alert(this.cow_name);
}

var cow = new MooCow();
cow.alertCowName();

This will as we except alert "SheLikesMilk". But what happens when one does an event? Let's put a click event on document that alerts the cows name:

var MooCow = function() {
  this.cow_name = "SheLikesMilk";
}
MooCow.prototype.alertCowName = function() {
  alert(this.cow_name);
}

var cow = new MooCow();
if (document.attachEvent)
  document.attachEvent("onclick", cow.alertCowName);
else if(document.addEventListener)
  document.addEventListener("click", cow.alertCowName, false);

This won't alert "SheLikesMilk", but "undefined"! This can be a major problem and this problem is caused by events, AJAX callbacks, timeouts etc. Basically function loses its scope and goes mad... Now there are number of solutions to this problem, check out following:

How this is solved in AJS

AJS is my own little JavaScript library. I have made AJS.bind that guarantees that a function will be called inside the desired scope. I have spent some time perfecting my function and here is how MooCow is done using AJS:

var MooCow = function() {
  this.cow_name = "SheLikesMilk";
}
MooCow.prototype.alertCowName = function() {
  alert(this.cow_name);
}

var cow = new MooCow();
AJS.addEventListener(document, "click", 
    AJS.bind(cow.alertCowName, cow));

This alerts "SheLikesMilk" and we are happy! The last line can also be written as:

AJS.AEV(document, "click", AJS.$b(cow.alertCowName, cow));

Basically we bind the function cow.alertCowName to the cow object, so this inside alertCowName will be the cow object and not window object.

The bind function for your use

It's really simple and beautiful - 9 lines of code:

function getRealScope(fn) {
  var scope = window;
  if(fn._cscope) scope = fn._cscope;
  return function() { return fn.apply(scope, arguments); };
}
function bind(fn, obj) {
  fn._cscope = obj;
  return getRealScope(fn);
}

In AJS the bind function can a little more (like passing extra arguments).

You basically call bind(fn, obj), where fn is a function and obj is an object (could be cow, window, document, DOM element etc.).

Remember that bind does not alter the function, but returns a new function. To bind a function fn to document, you would do:

var fn = function() {
  alert(this);
}
fn = bind(fn, document);
fn();

Especially, notice the last fn = bind...

Like this post? Digg it!

Code · Code improvement · Code rewrite · JavaScript 18. May 2006
© Amir Salihefendic. Powered by Skeletonz.