« The most informative iPhone article (Or why I haven't bought one yet!) | Main | July-August 2007 HBR Case Study: Monolithic Enterprise Software or SOA »

What is wrong with this widely used AJAX event handler registration code?

John Resig's blog post on Flexible Javascript Events presents cross-browser functions to register and deregister DOM events to/from any DOM element: addEvent() and removeEvent(). He wrote these functions in response to a addEvent() recoding contest, that was published at a well-known site for Web developers run by Peter-Paul Koch and included Scott Andrew LePera, Dean Edwards and John Resig himself as co-judges. The recoding contest itself was a response to wide interest in his blog post addEvent() considered harmful where he outlined a problem with a widely used function addEvent() published by Scott Andrew LePera. It should also be noted that John Resig's entry was judged as the winner entry.

Most web developers are familiar with the names mentioned in the previous paragraph. They have published books, maintain highly visible websites (Google PageRank of websites/blogs maintained by Peter-Paul Koch, Dean Edwards, John Resig, Scott Andrew LePera are 9, 8, 7 and 7, respectively at the time of this blog post), blog regularly and are generally considered gurus in the area of client side web development.

I add all this background only to make the point that writing cross-browser DOM event handling code is non-trivial and has attracted the attention of best minds in the field. With that feeling of comfort that comes with being in good hands, one would think that the problem, although considered difficult in the past, has been solved once and for all and can be reused without much thought.

At least this is what I thought till some strange behavior in my AJAX code that used John Resig's winning addEvent() and removeEvent() forced me to analyze each and every line of the whole program and discovered a couple of really interesting things about the addEvent() function. But before I get into my discovery, let us take a look at the addEvent() code from John Resig's page:


function addEvent( obj, type, fn ) { 
  if ( obj.attachEvent ) { 
    obj['e'+type+fn] = fn; 
    obj[type+fn] = function()
      {obj['e'+type+fn]( window.event );} 
    obj.attachEvent( 'on'+type, obj[type+fn] ); 
  } else 
    obj.addEventListener( type, fn, false ); 
}

As you can see, this code takes on two issues with IE's support for DOM events: (a) IE uses a non-standard method attachEvent() to register event handlers; and (b) it runs the handler code in the global context (ie; built-in variable this is set to window object during handler execution) and not in the context of the element to which the handler is registered.

The removeEvent() code is very similar and doesn't need to be reproduced here.

So, what is the problem? Actually, none whatsoever, at least not until you have an event handler function that is few tens of lines long and you pass the name of the function as the last argument to addEvent() function. If you are like me, you would think that the code will either use the function name string or some kind of address to create a short string as key to store the handler function reference within the DOM element object. But what really happens is that whole text of the handler function consisting of few tens of lines of code becomes part of the key (key is 'on' + type + fn). In my code I had a key with length greater than 2000! This in itself would not be much of a problem if the key was created only once during registration and then used for lookup during handler execution, though even a lookup in a hash table with very long strings is probably going to tax the JavaScript interpreter badly. The killer is that the key gets created every time the handler is run. This could be very frequent if the event type is 'mousemove' and could easily result in excessive memory use and sluggish behavior.

"This doesn't sound like an insurmountable problem," you may say, "just wrap your long function within another function that simply invokes the long function. This way the addEvent() code will use the body of the wrapper function for forming the key and avoid creation of long strings."

Actually, this is very similar to what I tried, my motivation bring two-fold: reduce the length of the code that gets used as part of the key and also pass an argument at the time of event handler registration. The wrapper creation function looked something like this:


function create_handler(func, arg1){
  return function(event){ 
    return lfunc.call(null, 
      event || window.event, arg1); 
  }
}

And I used it as follows:


function long_function(event, arg1){
 ... tens of lines of code ...
}
addEvent(obj, 'mousemove', 
  create_handler(long_function, arg1));

which, actually, ended up creating this fixed text for every function: "function(event){ return lfunc.call(null, event || window.event, arg1); }". As the key is a created by concatenating the even type and function text, same key will be created for different handlers if the event type remains same, causing overwrite! This actually happened in my code!

So, even the winning entry has skeletons in the cupboard. It is not that every use would result in broken programs, but there certainly are situations where they fall short. In fact, this is true for most library function and it is always a good practice to know not only the interface and purpose but also the underlying assumptions and how the thing actually works. To be fair to the author John Resig, the recoding contest post had a strict set of requirements and being a reusable function under different conditions was not one of those.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

About

This page contains a single entry from the blog posted on July 5, 2007 11:32 PM.

The previous post in this blog was The most informative iPhone article (Or why I haven't bought one yet!).

The next post in this blog is July-August 2007 HBR Case Study: Monolithic Enterprise Software or SOA.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.33