Thursday Jul 24, 2008

What's the Fastest Way to Code a Loop in JavaScript?

I built a loop benchmarking test suite for different ways of coding loops in JavaScript. There are a few of these out there already, but I didn't find any that acknowledged the difference between native arrays and HTML collections. Since the underlying implementations are different (HTML collections for example lack the pop() and slice() methods), benchmarks that don't test against both are probably missing important information.

My suspicions were confirmed. Accessing the length property is more expensive on HTML collections than on arrays, depending on the browser. In those cases, caching it made a huge difference. However, HTML collections are live, so a cached value may fail if the underlying DOM is modified during looping. On the other hand, HTML collections will never be sparse, so the best way to loop an HTML collection might just be to ignore the length property altogether and combine the test with the item lookup, since you have to do that anyway:

// looping a dom html collection
for (var i=0, node; node = hColl[i++];) {
    // do something with node
}

Another interesting result is that with HTML collections, hColl.item(i) is 2-6x slower than hColl[i], except in Safari where it's about the same. I wonder what the extra overhead is?

I've posted the results of all my benchmarks here, however my IE7 is really IE8 in IE7 emulation mode, so I mistrust these results as representing IE7's original JavaScript/DOM engine. If anybody wants to run the benchmarks in true IE7 and post the results, I'll update this post accordingly. Also, I'm sure there are ways of looping that escaped my imagination, so if you know of any, post it in the comments and I'll add them to the test page.

[Read More]

Monday Feb 11, 2008

A Look at Sun.COM's New Event Delegation Library

(A followup to my last post.) We're on the brink of releasing a new JavaScript mini-library to sun.com, which we call the reg library. It provides an object named, naturally enough, reg. It stands for register. With it, you can register behaviors, like this:

reg.click('ul.foo > li > a.bar', myFunction);

Once that bit of code runs, regardless of whether the entire DOM has finished loading into the browser, click events on elements matching ul.foo > li > a.bar will be handled by myFunction, which is passed a reference to the active element and the event object. This happens without any DOM traversal, and without any events ever being attached to any <a> elements. Even if the entire contents of document.body are subsequently overwritten by innerHTML, all those new element nodes implicitly inherit the correct behavior. No re-walking of the new DOM subtree ever occurs. No re-attachment of events ever occurs.

How is this even possible?

Two facts conspire to make this feasible. 1) The <body> (document.body) is available almost immediately. 2) Most events bubble. All you need to do is to stand on document.body, and you're privy to almost every event that occurs on the page, right out of the gate. No need to go seeking out the elements you need, they literally bubble their way to you. All you do is grab the event's target and ask the question, does this element, or one of its ancestors, match 'ul.foo > li > a.bar'? If so, run the associated function, if not, ignore it. This is really just event delegation, and it's nothing new, but we've made little use of it on Sun.COM before now.

Limitations, caveats, dangers

I harbor no illusions this is a perfect solution. There are limitations. Besides the fact that not all events bubble, much of time, behavior depends exclusively on preprocessing, especially if you're doing progressive enhancement. You don't want non-JS users to see useless expand/collapse signs or widgets laying around, so you build the widgets only if scripting is enabled. And the only time to build the widgets is onload. And the only way to build the widgets in the right place is... \*sigh\* traversal. Some of this can fortunately be avoided by relying as much as possible on CSS and making your widget styles depend on a dynamically-added class name, such as body.jsenabled, so there's some workaround potential at least.

There's also an inherent performance limitation. Sitting in the driver's seat on document.body isn't always a relaxing cruise through the countryside. It can easily turn into rush-hour gridlock, with a flood of events each demanding to be checked. For that reason, I dare not use this technique to handle mousemove events. That would cause a veritable torrent of events. Even mouseover events are iffy. We have the capability and it appears to work reasonably well, but time will tell whether it's really viable. Click events, on the other hand, because of their relative infrequency, are a good candidate. Of course, as we develop this thing further, we'll be looking for ways to mitigate performance risks.

So there you go

It isn't live yet, but hopefully will be soon. After this code has been chugging away out there in the wild and woolly world of production Sun.COM for a while, I may post more about this, what works, what doesn't, unexpected wins and losses, etc. Stay tuned.

Monday Feb 04, 2008

On the Inelegance of Current JavaScript Paradigms

As cool as unobtrusive JavaScript is for building the behavioral layer, it's nevertheless based on some pretty kludgy foundations, especially when contrasted with CSS's rather elegant method of declaratively building the presentation layer.

Kludge #1: Waiting for DOM load.

It seems unavoidable. If you want to be unobtrusive, there will be a delay before your functionality is available. Furthermore, DOMContentLoaded and friends don't truly solve the problem. Interestingly, a parallel problem exists in the CSS world: the FOUC (Flash Of Unstyled Content). FOUC is generally not a problem in modern web clients. I wish I could say the same for the "flash of behaviorless content" problem.

Kludge #2: Procedural DOM traversal and event attachment.

Why do I have to seek out the elements that interest me and add behavior, element by element? What a pain. On sufficiently complex pages this means walking the entire tree. If you add new elements as you walk the tree, hall-of-mirrors-style infinite loops and other pitfalls plague you. If innerHTML gets added during the course of the page's life, you have to go back and re-walk those parts of the DOM, re-attaching events as needed. Once again, contrast this with CSS, where the user agent abstracts away the traversal and the attachment of styles, allowing you to declare once--up front--which elements get which styles. Pity we can't do it this way for the behavior layer too.

So what?

Of course these are well known problems. Such is the hand we've been dealt, and tools and techniques exist that make these problems less painful, so why the fuss? In my mind anyway, leaky abstractions on top of kludges aren't an ideal state of affairs, and so I rant. But I also wanted to establish a little background for a future post where I'll describe some tools we're about to deploy on sun.com that, in certain instances, avoid these problems altogether.


[Update]: I've posted a followup with a bit of information about how our new library works.

About

My name is Greg Reimer and I'm a web technologist for the Sun.COM web design team.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today