JavaScript optimization Part 3: Attaching events

Posted by Dmytro Shteflyuk on under JavaScript

This is a third part of the JavaScript optimization tutorial, and today I’m going to talk about events. Sorry for a long delay between posts, I hope remaining parts would not be delayed so much.

Scenario: you have some elements and you need to add some actions to them (for example, when user moves mouse cursor over element, or clicks on elements).

This is pretty usual task in web-development. And the first thing that everybody knows is a different event attaching syntax in Internet Explorer and Firefox: first uses element.attachEvent, second — element.addEventListeners. Therefor you need to add code, which would detect what browser is being used and what method to use in each case. In the most popular client script library Prototype it is already standardized and you can always use Event.observe. Let’s benchmark a little.

I’m going to start attaching events from manual detection of which browser is being used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Attaching events
for (var i = items.length; i--; ) {
    if (items[i].addEventListener) {
        items[i].addEventListener('click', e_onclick, false);
    } else if (items[i].attachEvent) {
        items[i].attachEvent('onclick', e_onclick);
    }
}
// Detaching events
for (var i = items.length; i--; ) {
    if (items[i].removeEventListener) {
        items[i].removeEventListener('click', e_onclick, false);
    } else if (items[i].detachEvent) {
        items[i].detachEvent('onclick', e_onclick);
    }
}

This approach shown best times: 188 and 203 ms in Internet Explorer 6 and 7, 125 and 141 ms in Firefox 1.5 and 2.0, and 63 ms in Opera 9, but there is one problem with it — memory leaks: Internet Explorer usually forget to clean up memory, used by event listeners, when you navigating to another page. Therefor all JavaScript frameworks which implement event attaching/detaching functions, provide functionality for removing listeners when you navigating to another page. Let’s test them.

Code for testing Prototype library:

1
2
3
4
5
6
7
8
// Attaching events
for (var i = items.length; i--; ) {
    Event.observe(items[i], 'click', e_onclick, false);
}
// Detaching events
for (var i = items.length; i--; ) {
    Event.stopObserving(items[i], 'click', e_onclick, false);
}

This is very slow, 6453 ms in Internet Explorer 6 and looks like some memory leaks are still exists (it became slower and slower with the lapse of time) and 365 — 653 ms in other browsers.

There are tons of problems and solutions with adding events (just look at the Advanced event registration models). Some time ago even PPK’s addEvent() Recoding Contest was over (but do not use the winner’s solution because of it’s inefficiency and memory leaks).

Instead of winner’s solution, I decided to test incredible and impertinent solution from Dean Edwards, which even does not use the addeventListener/attachEvent methods, but it is entirely cross-browser!

Used code:

1
2
3
4
5
6
7
8
// Attaching events
for (var i = items.length; i--; ) {
    addEvent(items[i], 'click', e_onclick);
}
// Detaching events
for (var i = items.length; i--; ) {
    removeEvent(items[i], 'click', e_onclick);
}

Results are as much impressive as when we used manual events attaching, and this approach is extremely fast to use it in real-world applications. Of course, you would say: “Stop, what you are talking about? I don’t want to use additional methods while Prototype is already used in my code!” Calm down, you don’t need to do it, just download Low Pro library (current version is 0.4, stay tuned). Ruby on Rails developers could use UJS Rails Plugin in their applications to improve performance — it uses Low Pro inside.

No Method IE 6 IE 7 FF 1.5 FF 2.0 Opera 9
1 manually 203 188 125 141 63
2 prototype.js 6453 653 547 469 365
3 addEvent 783 344 94 141 62

Benchmark: Attaching events

You can review benchmark and get your own results here.

Conclusions

  • Always use Low Pro along with Prototype.js (of course, if your application uses rich client logic).
  • Use manual events attaching when you need to get as much as possible speed from your application (for example, when you handling hundreds of elements). But don't forget about memory leaks!
  • Avoid events attaching whenever it's possible (for example, use :hover CSS selector instead of onmouseover event).
  • Keep your eyes opened, maybe tomorrow somebody would post another great performance optimization tip.

Links to other parts

10 Responses to this entry

Subscribe to comments with RSS

said on May 10th, 2007 at 12:47 · Permalink

Optimalizace JavaScriptu…

Dmitrij Štefljuk píše velmi zajímavý seriál o optimalizaci JavaScriptu. Doporučuji všem, co se zabývají JavaScriptem.

Enrique Melendez
said on May 17th, 2007 at 13:13 · Permalink

Great article,
One thing to take into consideration about applying events (apart from “how”) is “where”.

I mean, for example, it’s far better to attach ONE “mouseover” event to a whole “tbody” and capture the “td” source which fired it than to attach many of “mouseover”s to ALL “td”s…

So, for speed reasons it is crucial “where” do you attach your events…

said on May 17th, 2007 at 16:05 · Permalink

Thank you, Enrique
You right, of course. It’s better to use one event observer for parent element instead of separate observers for each child node. Great tip!

said on January 16th, 2008 at 12:54 · Permalink

очень много дельных советов можно почерпнуть из этой статьи

ps. OpenID не отработал

1
Fatal error:  Call to undefined function curl_init() in /var/www/kpumuk/kpumuk.info/wp-content/plugins/openid/openid-classes.php on line 229
said on January 16th, 2008 at 13:07 · Permalink

Спасибо за ссылку и за баг :-) Будем посмотреть

Kniaź
said on February 3rd, 2008 at 18:56 · Permalink

I’m sure also, that Your code using “manual” events setting would be little faster if you checking event’s attaching methods existence once. On attaching example:

1
2
3
4
5
6
7
8
9
10
// Attaching events
if (items[0].addEventListener) {
    for (var i = items.length; i--; ) {
        items[i].addEventListener('click', e_onclick, false);
    }
} else if (items[0].attachEvent) {
    for (var i = items.length; i--; ) {
        items[i].attachEvent('onclick', e_onclick);
    }
}

Look also on brilliant idea in one of last slide of Dan Webb’s presentation here:
http://www.danwebb.net/2007/11/22/media-ajax

btw. thx for the link to Low Pro :-)

ror
said on January 9th, 2009 at 12:12 · Permalink

UJS Rails Plugin rules!

Comments are closed

Comments for this entry are closed for a while. If you have anything to say – use a contact form. Thank you for your patience.