Although the test in question is fantastic, the general conclusion drawn by the author is actually incorrect. What the test does is grab all of the cells in a table using
getElementsByTagName. It then walks the cells and in one case changes the class name of the element and in the other just updates the inline style.
However, a key subtlety of the list returned by
getElementsByTagName is that it isn’t really a list at all. It’s more like a live query of the document. This live query is actually extremely complicated to implement well in terms of performance, because it always has to reflect the correct set of nodes, even when dynamic changes are made in the DOM.
Thus this seemingly simple test is actually far more complex than you might think. In Safari we attempt to cache information as you browse this live query, remembering both the length and the last item you looked at, on the assumption that the most common operation will be a forward iteration through this “array” of objects. However if the DOM changes, the cached length and item can become invalid, because – again – this is not a simple list. It is a live view of all the nodes that match the query.
In the case of setting
className, Safari updates the class attribute immediately. For inline style Safari is more sophisticated. Although it updates the parsed declaration, it does not bother to update the corresponding style attribute immediately. It simply marks it as dirty and will update it only if someone asks for the value. This means that in the case of repeated updates to class names, the DOM is changing in Safari, but in the case of repeated inline style updates, it is not.
Here’s where the real bug in Safari rears its ugly head. The cached state for the query that keeps iteration through it fast was getting improperly invalidated when attributes changed and not just when elements and nodes were added and removed. This meant that you were recalculating the length of the list at every iteration and crawling to find the nth item at every iteration. Even though the act of setting a class name is much faster than inline style, the use of this live query (and a bug in it, not in the style system) leads to much slower performance.
I fixed our code to properly invalidate only when elements are added and removed, and am happy to report on my 1.33 GHz laptop that I got 100ms on the test of inline style setting and 49ms when setting the class name. Thanks to ppk as usual for writing a great test case and uncovering a nifty bug in our implementations of live queries from getElementsByTagName, even if he did come to the wrong general conclusion.
I think it would be interesting to see a page that tried walking the DOM using all the different possible means to see which of the techniques is the fastest and to help browsers ensure that all possible ways of “walking the DOM” stay fast. Examples that come to mind are
getElementsByTagName, HTML collections for images and links,