Bug Tracker

Ticket #5087 (closed bug: patchwelcome)

Opened 5 years ago

Last modified 15 months ago

Use element.classList if available to speed up class manipulation

Reported by: sylvain Owned by: rwaldron
Priority: low Milestone: 1.next
Component: attributes Version: 1.3.2
Keywords: Cc: rwaldron
Blocking: Blocked by: #5766

Description

Element.classList is a new API introduced in HTML5 for manipulating the classes of an element. The API is documented on  https://developer.mozilla.org/en/DOM/element.classList.

This API is implemented in Firefox 3.6 and WebKit is planning to implement it.

JQuery could detect if this property is available and use it in the {add,had,remove,toggle}Class functions. A 3-90 speedup could be achieved with Firefox according to this benchmark:  https://bug501257.bugzilla.mozilla.org/attachment.cgi?id=395627).

Change History

comment:2 Changed 5 years ago by dmethvin

  • Component changed from unfiled to selector

comment:3 Changed 4 years ago by rwaldron

  • Owner set to rwaldron
  • Status changed from new to assigned

comment:4 Changed 4 years ago by rwaldron

  • Priority changed from minor to low
  • Milestone changed from 1.4 to 1.5

comment:5 Changed 4 years ago by paul.irish

#7378 is a duplicate of this ticket.

comment:6 Changed 4 years ago by paul.irish

  • Blocked by 5766 added

comment:7 Changed 4 years ago by MT

Apparently element.classList is currently implemented in both Firefox (3.6+) and Chrome (at least 8.0.552.237+). Two independent implementations are quite good reason to use this native feature in jQuery to improve performance.

comment:8 follow-up: ↓ 9 Changed 4 years ago by rwaldron

There has been extensive research into the practical application of using classList internally - we're not in any rush to wrap two very different logic scenarios for a small perf boost

comment:9 in reply to: ↑ 8 Changed 4 years ago by MT

Replying to rwaldron: Small boost? Do you have some exact numbers? What do you think about benchmark results mentioned in the initial topic message? Thanks.

comment:10 Changed 4 years ago by rwaldron

@MT I probably shouldn't have used a quantifying adjective.

I'm revisiting the benchmarks I wrote last fall.

comment:11 Changed 4 years ago by rwaldron

When you make classList do what jQuery does (which is what it would have to do if it was used in the implementation), with regard to multiple classes, its not faster:

 http://jsperf.com/classlist-vs-addclass

I'd like to note that when I wrote a 5 class, 100 element test, the classList functions were faster in some cases.

Last edited 4 years ago by rwaldron (previous) (diff)

comment:12 Changed 4 years ago by MT

Replying to rwaldron: Thanks, this is interesting. Nevertheless, adding multiple classes to one element at once is quite rare (since just useless) operation. So, such benchmarks are not enough representative.

At the outside, classList might be used for adding single class, while pure script implementation—for adding multiple classes.

comment:13 follow-up: ↓ 14 Changed 4 years ago by rwaldron

Is it really worth it? To add code that checks for support of classList and checks if the split string has length > 1 then use classList...? Seems like a lot of work

Trust me, I'm the biggest fan of classList and even proposed a new function call classlist() - that was faster in many regards.

comment:14 in reply to: ↑ 13 Changed 4 years ago by MT

Replying to rwaldron:

Is it really worth it? To add code that checks for support of classList and checks if the split string has length > 1 then use classList...? Seems like a lot of work

From practice perspective, I believe that adding single class is used far most often than adding multiple classes at once. So, if using native classList will result in any performance boost with single class case, then why not? ;-) We can even accept small perfomance loss in multiple class adding operation at all (to exclude an additional check for whether one or multiple classes are added) since this is rarely used.

proposed a new function call classlist() - that was faster in many regards.

What is the function you are talking about? Some potential jQuery.classlist()? Can you provide a details?

comment:15 follow-up: ↓ 17 Changed 4 years ago by rwaldron

Have you looked that jQuery UI source? Some examples:

jquery.ui.tooltip.js, L37
addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content")

jquery.ui.tabs.js, L 146
addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )

jquery.ui.tabs.js, L 160
this.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" );

jquery.ui.tabs.js, L 161
this.list.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" );

jquery.ui.tabs.js, L 162
this.lis.addClass( "ui-state-default ui-corner-top" );

jquery.ui.tabs.js, L 163
this.panels.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" );

Even more... 
jquery.ui.tabs.js, L 210, 214, 283, 291, 300, 307, 419



You get the idea ;)

Version 0, edited 4 years ago by rwaldron (next)

comment:16 Changed 4 years ago by paul.irish

There's a pull req for this now:  https://github.com/jquery/jquery/pull/228

comment:17 in reply to: ↑ 15 Changed 4 years ago by MT

Replying to rwaldron: When native feature is available, it is the only true way to do things and is preferred over pure script implementation anyway. Native implemenation is at least much more likely to work correctly than pure-script one.

For example, boolean attribute selector ('INPUT[autofocus]') in jQuery works only in browsers that support native querySelector() (see bug 5637). And this is not because native feature is faster or not at all, but because this is native.

An exception for preferring native implementation is when this is entirely (in any use case) and considerably (by more than, say, 1.5x) slower than pure-script one. (Though, even in that case, native implementation may get faster in future versions of browsers while pure-script one will most likely not.)

As for classList() method you've mentioned, then, in my opinion, this might only return list of element classes. For adding multiple classes as JS-array, just regular addClass() should be used (impossibility to add classes as Array object is actually drawback of current addClass() method).

Thanks.

comment:18 Changed 4 years ago by rwaldron

#8447 is a duplicate of this ticket.

comment:19 Changed 4 years ago by danheberden

  • Keywords pullreq added
  • Milestone set to 1.next

comment:20 Changed 4 years ago by john

  • Status changed from assigned to closed
  • Resolution set to patchwelcome

See my comments in the closed pull request (classList appears to be too slow right now). If classList gets faster, or if a better patch is proposed, we'll happily land it.

comment:21 Changed 4 years ago by john

  • Component changed from selector to attributes

comment:22 Changed 3 years ago by rwaldron

#10196 is a duplicate of this ticket.

comment:24 Changed 3 years ago by rwaldron

  • Status changed from closed to reopened
  • Resolution patchwelcome deleted

I'm going to re-open this for possible 1.8 proposal.

comment:25 Changed 3 years ago by addyosmani

  • Cc rwaldron added

Some quick comments: looking through the thread it would appear that the consensus was that for getting jQuery to do with classList what we currently do with multiple classes it was considered too slow to use. This also applied to the pull request that came through a few months ago.

Are there particular performance tests or implementations we're currently aware of that perform better than previously observed or is the intention of the discussion to seek a better implementation that works around what vendors currently have so that we can support this?

(just curious) :)

comment:26 Changed 3 years ago by rwaldron

I'm just going to revive local branches I have and run perf tests. Thees a few things I want to try this time around

comment:27 Changed 3 years ago by dmethvin

I'd leave the ticket closed unless there's a clear reason to revive it based on new benchmarks for example.

comment:28 Changed 3 years ago by timmywil

  • Keywords pullreq, 1.8-discuss added; pullreq removed
  • Status changed from reopened to open

comment:29 Changed 3 years ago by rwaldron

  • Keywords pullreq, removed

The performance issue of setting multiple classes still exists.

comment:30 Changed 3 years ago by rwaldron

  • Keywords 1.8-discuss removed

comment:31 Changed 3 years ago by dmethvin

  • Status changed from open to closed
  • Resolution set to patchwelcome

OK, closing this yet again because it provides no performance benefit. Let's leave it closed until there is a pull request that shows it can make things faster without a lot of extra code, since it definitely will introduce a completely different code path.

comment:32 Changed 3 years ago by rwaldron

I'll be paying attention here:  https://www.w3.org/Bugs/Public/show_bug.cgi?id=13999

If it can bring the multi-class piece of the puzzle to the table, I will start looking at this again.

comment:33 Changed 3 years ago by rwaldron

@paul_irish just pinged me about some perf improvements happening here  https://bugs.webkit.org/show_bug.cgi?id=80376#c2

comment:34 Changed 3 years ago by rwaldron

comment:36 Changed 3 years ago by dmethvin

Well, the 10x improvement boiled down to 2x at our API level; not nearly as impressive once you apply the jQuery semantics like multiple class add/remove.

comment:37 Changed 2 years ago by dmethvin

#12027 is a duplicate of this ticket.

comment:38 Changed 2 years ago by m_gol

IE9 is now the only browser supported by jQuery 2.0 that doesn't have the classList API implemented. Too bad, otherwise the argument about two code paths wouldn't be so severe as they would be two code paths in two separate jQuery versions.

What's your current position on this?

Current perf results: Chrome Canary: 2x faster, IE10: 2.35 faster, Fx 18 (current stable): 3.8 times faster when using classList. IMHO it's worth a shot.

comment:39 Changed 2 years ago by rwaldron

The position is: until it supports _multiple_ classes at the same speed as jQuery, it remains a non-starter.

"me too" doesn't make specs update faster.

comment:40 Changed 22 months ago by m_gol

I was really curious if spec imperfection is the blocker here so I implemented classList utilization in attributes.js (with fallback to className for IE9). Here's the pull request:  https://github.com/jquery/jquery/pull/1190

I also created a jsFiddle and run it in a couple of browsers:  http://jsperf.com/classlist-v-old-way/5

As you see, the only case where we get a (really slightly) worse performance is with addClass("many class names here"), the rest is significantly faster with the modified code. The most significant difference is with hasClass and also when adding/removing single classes (which is one of the most common uses, I suppose).

Here's a test case with 10 classes instead of 100:  http://jsperf.com/classlist-v-old-way/7 The difference is still significant.

I think it's worth to pull this patch, what do you think?

comment:41 Changed 22 months ago by rwaldron

Thanks for the contribution, but jQuery won't use classList until it can be used to completely replace the current code

comment:42 Changed 22 months ago by m_gol

Since my patch won't go into the core and I think it can still be useful in cases where one focuses mostly on modern browsers, I created a jQuery extension redefining class methods to utilize classList:
 https://github.com/mzgol/jquery.classList

I removed checking for classList support so the code won't work in IE9 but one can use a polyfill for classList for this browser (or just filter my code out by conditional comments for IE<10)

I also modified test cases to utilize this code:
 http://jsperf.com/classlist-v-old-way/8
 http://jsperf.com/classlist-v-old-way/10

Both these test suites confirm performance gains up to 10x in some cases. Have fun!

Last edited 22 months ago by m_gol (previous) (diff)

comment:43 Changed 22 months ago by m_gol

Good news: multiple arguments for classList.add & remove are already supported in WebKit nightly and Chrome stable. Unfortunately, no such change for toggle as the second argument is reserved for the boolean flag...

I updated my little project to detect support & account for that.

comment:44 Changed 22 months ago by rwaldron

@m_gol can you do me a favor and create a fiddle demonstrating the updated implementation and link it here? This will be helpful for us to periodically revisit the API progress :D

comment:45 Changed 22 months ago by m_gol

Two jsFiddles I pasted before contain test cases utilizing the latest version of my patch. I'll keep them up to date in README.md so you can just bookmark:
 https://github.com/mzgol/jquery.classList

If you have any suggestions to test cases or anything else related to my patch, feel free to ping me.

Glad to help!

comment:46 Changed 22 months ago by m_gol

Mozilla bug reports:
 https://bugzilla.mozilla.org/show_bug.cgi?id=814014 - about accepting multiple parameters to add/remove
 https://bugzilla.mozilla.org/show_bug.cgi?id=814090 - about toggle accepting a boolean as the second parameter

comment:47 Changed 22 months ago by m_gol

WebKit bug report:
 https://bugs.webkit.org/show_bug.cgi?id=111307 - div.classList.add('a', 'a') creates duplicate entries

Is it OK to post such updates here? It seems on topic but I don't want to spam people with excessive notifications.

comment:48 Changed 22 months ago by rwaldron

Please do!

comment:49 Changed 22 months ago by dmethvin

Yes, it's good to have a list of related bugs here, thanks m_gol!

As for the patch, I think we're still comfortable with this being in a plugin that shimmed .addClass() since it would be very rare that className manipulation is a performance issue and the additional code paths create testing/maintenance/debugging issues. Especially while the browsers have bugs in their implementations.

comment:50 Changed 22 months ago by alnitak

I've also created a plugin that replaces .hasClass, .addClass and .removeClass with versions that use element.classList if available, and which fallback to the original functions for the more unusual cases (i.e. multiple classes, function parameters, etc).

 https://gist.github.com/raybellis/5115997

Last edited 22 months ago by alnitak (previous) (diff)

comment:52 follow-up: ↓ 54 Changed 17 months ago by s@…

The biggest reason I can see to use classList, aside from the 200-300% speedups we're seeing in jsperf, is the elimination of the really awful performance implications of addClass() and removeClass() on large lists. For example, the naive way to remove all elements with class '.selected' from a long list would be $('.item').removeClass('selected') which invalidates the style of every single .item div, causing the next layout to be very expensive.

It's important to see the performance differences in the context of the subsequent layout, as not many of the tests thrown around show it. I've put together a jsperf that should illustrate my meaning.

 http://jsperf.com/classlist-remove-vs-jquery-2

comment:53 Changed 17 months ago by dmethvin

Just checking for the class actually being there has a positive effect. I'm also not sure why the two cases you've identified (removeClass with better selector) are so different.

comment:54 in reply to: ↑ 52 Changed 17 months ago by jdunck

Replying to s@…:

I've added a ticket (and a related pull request) to avoid assigning to className if the resulting value is the same.

http://bugs.jquery.com/ticket/14250

I think this would avoid the specific case described here (where most of the elements shouldn't be touched).

The biggest reason I can see to use classList, aside from the 200-300% speedups we're seeing in jsperf, is the elimination of the really awful performance implications of addClass() and removeClass() on large lists. For example, the naive way to remove all elements with class '.selected' from a long list would be $('.item').removeClass('selected') which invalidates the style of every single .item div, causing the next layout to be very expensive.

It's important to see the performance differences in the context of the subsequent layout, as not many of the tests thrown around show it. I've put together a jsperf that should illustrate my meaning.

 http://jsperf.com/classlist-remove-vs-jquery-2

comment:55 Changed 15 months ago by m_gol

comment:56 Changed 15 months ago by dmethvin

N.B., I think the reason for at least some of the perf differences were that until 1.11/2.1 jQuery reassigned the className property and most browsers force layout on that *even if the classes are unchanged*. So that's what is behind my puzzlement in comment 53 above.

It's also highly likely likely that any needed reflow would totally overwhelm a performance benefit from going native here, no? The jsperf may not be reflecting this critical issue.

comment:57 Changed 15 months ago by rwaldron

It would be great if folk just stopped being obsessed with this ticket. As owner, I've committed to keeping up to date with the native implementation and jQuery is still faster. I haven't forgotten, it's not being ignored and we're not going to have multiple code paths.

comment:58 Changed 15 months ago by m_gol

@rwaldron Hey, I'm not obsessed about anything. When I asked 7 months ago in a comment if it's OK to post here links to bug reports related to the topic both you and Dave advised me to do it. I can stop if you changed your mind...

comment:59 Changed 15 months ago by dmethvin

@rwaldron it's because of the recent tweets about it, don't worry. My thoughts on this haven't changed; even if there's a 2x speedup there's no evidence that it makes a meaningful performance difference in real code, and the resulting layout will be overwhelmingly larger than the work required to change the className string. Plus, there's m_gol's plugin for people who are trying to update thousands of classNames per second and finding it too slow.

Note: See TracTickets for help on using tickets.