I have spent a lot of time coding in Javascript lately, and a lot of that time has been iterating through arrays. I quickly discovered this is no simple task due to the way Javascript works.

Scope and binding

Scope is a really tricky concept in Javascript, you can read a nice explanation of this concept on the "Alternate idea blog":http://alternateidea.com/blog/articles/2007/7/18/javascript-scope-and-binding

So here is my recap of this problem:

Take the following simple script:

window.hello = "ninja";
var MyClass = Class.create ({
    test: function()
    {
      this.items = $A([1]); 
      this.hello = "hello";
      this.items.each(function(i)  
        {
            alert(this.hello);   
        });
    }
  }); 

  var a = new MyClass(); 
  a.test();

If you run it you may be surprised to see the output "ninja". Well how is this possible, in a nutshell, unlike other programming languages the "this" keyword refers to your current scope. When I am showing the alert message my scope is no longer the MyClass object it is the window object. So there you go, I have a Ninja.

I don't want a Ninja

The first pass of fixing this issue is here:

window.hello = "ninja";
var MyClass = Class.create ({
    test: function()
    {
      this.items = $A([1]); 
      this.hello = "hello";
      this.items.each(function(i)  
        {
            alert(this.hello);   
        }.bind(this));
    }
  }); 

  var a = new MyClass(); 
  a.test();

The "bind":http://www.prototypejs.org/api/function/bind function in prototype will call the "apply":http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Function:apply Javascript function. This allows you to switch the meaning of the "this" keyword to refer to a different object - in this case its the MyClass object.

It works but I do not like the flow. The bind statement looks, to me, out of place.

Making the code flow better

Well, the longer the body of the function in the each loop is the weirder this looks.

Off we go to the prototype documentation and we discover we can pass the context to the bind function, so here it is re-written

window.hello = "ninja";
var MyClass = Class.create ({
    test: function()
    {
      this.items = $A([1]); 
      this.hello = "hello";
      this.items.each(function(i)  
        {
            alert(this.hello);   
        }, this);
    }
  }); 

  var a = new MyClass(); 
  a.test();

What happens if we swap things around

I find this code a bit more readable, but why is the ", this" hanging out at the end of my iteration?

What I want is this:

window.hello = "ninja";
var MyClass = Class.create ({
    test: function()
    {
      this.items = $A([1]); 
      this.hello = "hello";
      this.items.each(this, function(i)  
        {
            alert(this.hello);   
        });
    }
  }); 

  var a = new MyClass(); 
  a.test();

Looks easier to read, and given the limits of the Javascript language it's the best I could come up with, but it will require some prototype hacking.

Which you can read here:

Object.multiExtend = 
  function(destinations, source)
  {
    destinations.each(function(destination) {
      Object.extend(destination, source); 
    }); 
  }


Enumerable.old_each = Enumerable.each; 
Enumerable.each = function(arg1, arg2)
{
  if (Object.isFunction(arg1))
  {
    this.old_each(arg1,arg2);
  }
  else 
  {
    this.old_each(arg2,arg1);
  }
}

Object.multiExtend(
  [Array.prototype, Hash.prototype, ObjectRange, Ajax.Responders, Element.ClassNames.prototype], 
  {
    old_each: Enumerable.old_each,
    each: Enumerable.each
  }
);

I basically change the each function so it can be called with the object name as the first param while maintaining backwards compatibility. Note, there is no central registry of all the Objects implementing Enumerable so I am stuck hard coding it.

Comments


comments powered by Discourse