Your jQuery: Now With 67% Less Suck
54 Comments
Comments are ordered by helpfulness, as indicated by you. Help us pick out the gems and discourage asshattery by voting on notable comments.
Got something to add? You can leave a comment below.
Alex Michael
Rob L.
Useful tips all. One mini-pattern that I’ve picked up from co-workers is, when caching selectors, prefix your variable with $ to note that it’s a jQuery object. This can be really helpful in methods where you’re dealing with lots of variables, especially when you come back later to modify them or fix bugs.
var $blocks = $(”#blocks”).find(“li”);
Jason Hummel
If you’d rather not play around with all that messy string concatenation when performing costly DOM manipulations, give createDocumentFragment a try.
var div = document.createElement(“div”);
div.appendChild( document.createTextNode(“Test div to be inserted 100 times”) );
var fragment = document.createDocumentFragment();
for(i = 0; i < 100; i++) {
fragment.appendChild( div.cloneNode(true) );
}
document.getElementById(“container”).appendChild(
fragment.cloneNode(true)
);
Amazingly the browser support for documentFragments goes back to IE6.The benefit comes from the ability to add the entire fragment DOM tree into the page DOM with one insertion – just like the method outlined in the article.
Nice article.
Last example can perform even better if you use array joining instead of string concatenation.
var arr = [reallyLongArrayOfImageURLs],
tmp = [];
$.each(arr, function(count, item) {
tmp.push(’<li><img src=”‘item’”></li>’);
});
$(’#imgList’).append(tmp.join(”“));
Mathias Bynens
Nice write-up, Scott!
Just a few clarifications:
* `document.querySelectorAll(’:hidden’)` doesn’t actually work; in other words, supporting QSA doesn’t affect the performance of jQuery/Sizzle’s custom selectors. (The text makes it seem like it does.)
* The reason Opera scores so well in QS/QSA tests is because it caches the results. It would be tricky but interesting to create a performance test case that works around this by avoiding repeated selectors.
Paul Irish
@Matthew.. “Would it be possible for jQuery to detect when an ID is the first bit of a selector string and … do a getElementById call “
It does that. :)
https://github.com/jquery/jquery/blob/2a63b98/src/core.js#L141-143
Joe Mottershaw
This has been a very good read, it’s made me more aware of how sluggish my jQuery code actually is compared to how it could be!
I have a question though, you mentioned caching, how does this fair with using $(this). For example, if you only called upon an element once in your script for a click detection, would you need to assign it to a variable first if that element was the same element also be altered? Some examples…
1st Example
$(”#div_id”).click(function() { $(this).hide(); });
2nd Example
var div_id = $(”#div_id”);
div_id.click(function() { div_id.hide(); });
Apologies for the bad example…
But is there much of a difference in loading time between the two?
Is $(this) a sort of variation of caching as it’s already selected the element and you’re just calling it back by saying “this one, the one you just found, yeah, use that”, or is it having to wade through all the code again to find it?
JulienW
I also strongly disagree with the last part. Not about its speed but about its security. Really, doing string concatenation should light a warning in your head ! And I keep seeing these advice on every website talking about jQuery !
Really, what is faster here is that you don’t access the node at each step of the loop. You can do this by adding elements to an “off-DOM” node. Yes, it will probably be slower than string concatenation, but still way faster than using an attached DOM node.
So instead of using string concatenation, use jQuery’s “attr” function on a unattached DOM, and it should be nearly as fast.
jsperf testcase to demonstrate this
(feel free to fiddle with the testcase as it doesn’t seem so reliable on my firefox right now)
Andi Farr
Julien: unless I’m reading it wrong, your test seems to show that appending to an off-DOM node is considerably slower than simply adding to the DOM directly in all browsers tested.
You can make it slightly faster by adding the src attribute when you create the img element ($(’<img>’, { ‘src’ : item });) rather than using a seperate attr() call, but it’s still slower than just appending straight to the DOM – pretty surprising, not what I would have expected.
Cheers!
Josh Hartman
Really nice article Scott, and thanks for the intro to the .on() method in jQuery 1.7.
dave mellett
Excellent article with some good tips. Be careful with caching when using ajax. New nodes added to the page will not exist in the cached selector, you’ll have to re-cache. Or even better, use .live() – or .on() in v1.7 I believe.
Alan Moore
This article was a source of one excellent piece of information for me: that .live() had been replaced by on().
I’m surprised that .live() hasn’t had more exposure with the rise of media queries and responsive design: if your page has an element hidden at its initial size (or added to the DOM when the size changes), then jQuery can’t attach, say, a ‘click’ to that element, since it’s not in the tree. Change your page width, add the element back in and… no jQuery action on click.
Rather than $(‘a’).click(function() { … }); we use $(‘a’).live(‘click’, function(event) { … }); instead. It’s very powerful, but I was not aware that live() has been deprecated. Now I know better, and I will use
$(‘a’).on(‘click’, function(event) { … }); instead!
As for the performance issues, I must just not be doing enough with my scripts to tax the browsers. I develop in Safari, then Chrome, then Firefox. I test IE inside virtual machines with only 512Mb apiece and none of them have any performance problems. If I start to spot any, I’ll be back here to see what I can do…
pete weissbrod
I ran some of the js perf tests using internet explorer 9.0.8 and in quite a few instances I was surprised to see it outperform chrome. The new JS engine is extremely strict as I discovered when trying to use processing.js
Adam
“The IE JavaScript engine moves at the speed of an advancing glacier compared to more modern browsers, so optimizing our code for performance takes on an even higher level of urgency.”
less than 9, yes I agree. But the engine in IE9, from what I’ve observed is better than any other browser, even those versions that have come along since IE9 was released.
Stephen Woodworth
Thank you!!! This is gold, especially appreciate seperating religon from science with samples and data.
Jose Galdamez
I had no clue about the on() method in v1.7. After seeing it in use here it definitely makes more sense to attach event handlers to parent elements as you did in your table example.
In your section on DOM manipulation, are you also implying that it’s faster to prepend/append strings as opposed to DOM nodes? I’ve heard the former is faster, but never actually done any speed tests myself.
I noticed one small typo. document.getElementsByTagname should be document.getElementsByTagName (capitalize the “n” in “Name”).
Excellent post!
Fantastic, specially liked the .find() optimization.
For someone coming from ActionScript jQuery shares lots of optimizations.
But DOM manipulation is the heavyweight here.
I was expecting a mention to the second parameter of jQuery(), the context parameter. I was under the impression it narrowed the scope of the DOM search. Is it really faster? or is .find() better.
I also have the doubt as to how does jQuery reads selectors. Is it like CSS (from right to left) or always goes for the ID first?
Scott Kosman
Thanks for the feedback and questions, everyone. I’ll try to get to them all in time, today’s going to be a little crazy around the office, though, but I’ll do what I can.
One quick response in regards to selector performance, right-to-left, context vs. .find(), etc.: this jsperf example runs through various methods and in almost all browsers (Opera being the lone exception), .find() outperforms other selectors.
Caveat: all methods get obliterated by native JavaScript (see revision 69 of the same test for proof – I created rev 70 and removed the native version just to make the graph readable), but still.
@Andi: yes, those two methods will generally yield pretty similar results. Using .find() for me is more force of habit than anything else.
JulienW
As seen on this jsperf testcase : http://jsperf.com/various-jquery-testcases
This is also way faster to use :
$(document.getElementById(“foo”));
than
$(”#foo”);
But really, is it really so important ? Most of the time, you just cache the result of the selector in a var once, and use it after this.
Scott Kosman
Good question, Bertram. I’d (almost, see note below) always recommend using the latest version of the library. The addition of .on() is only one change of many since 1.4, there have been an absolute ton of additions and improvements over the last few releases.
Besides, once the libraries are minified and gzip’d the file size difference will only be (by my best estimate) about 6 or 7 kb.
The only time I’d be wary of going with the latest version is when updating the version of jQuery used in an existing project. Though updating is almost always painless, the chance of something breaking in your existing code when moving to a new version of jQuery is always present. When I’m using the Google Libraries API to load jQuery (which is almost every project) I will always specify the exact point version I’m loading rather than relying on it to just feed me the latest version. When a new version of the library gets rolled out I’ll switch to the updated version in a dev environment and make sure nothing’s broken before updating on live.
Scott Kosman
Bam! Here it is!
http://www.learningjquery.com./2009/03/43439-reasons-to-use-append-correctly
It’s a couple of years old but the idea is still solid. Pushing values into an array on each iteration and then using $(“whatever”).append(myreallylongarray.join(’‘)); to do your heavy lifting shows HUGE performance boosts. In retrospect this is the “right way” example I should have used for the DOM Manipulation section of my article, but hindsight is 20/20, etc.
Alex Rohde
Interesting article, objective, useful, and well-supported. I really liked the part on event delegation.
One thing I’m noticing is a schism in the developer community around conflicting sets of standards (performance, aesthetics, reusability).
For example, though inserting inline HTML for DOM manipulaiton may be the fastest method, I recently was turned down an interview because a code sample did just this (which was a violation of separation of concerns or some such philosophical thing).
Thanks for this great article !
Here’s the test for the last part, ‘DOM Manipulation’, quickly thrown together, with a notSoLongImageArrayFromFlickr:
http://jsperf.com/jquery-dom-manipulation
Now, off to test it with a lot of different browsers :)
JulienW
here is the test for the first part :
http://jsperf.com/find-vs-direct-selector
Using find is actually faster, but not so much.
Matt Hinchliffe
For further reading I would also recommend the following slides from jQuery core team member Addy Osmani and for some bed time reading: jQuery Fundamentals
Scott Kosman
I’ve gotten a few questions on the Twitters about multiple classnames, and how $(”.class”).find(”.other”) compares with $(”.class .other”). I’ve knocked together a really quick jsperf for that one, though I’ve only tested it in Safari/OS X so far. Hit it in different browsers and let’s see what’s the haps!
RIchard Powell
Really great article. The biggest surprise for me was that element selectors perform so well.
Rodney Rehm
Kinda shocking how slow jQuery actually makes inserting nodes into the DOM
Andreas Lagerkvist
Some nice tips here. I’m curious, what’s the difference between .on() and .live()? To me it seems they should both do the same thing so I don’t really get why .on() was added and what the potential differences between them are?
Scott Kosman
@Andreas: with jQuery 1.7 the .live() method has officially been deprecated. The .live() API docs page lists a number of reasons why.
Chiefly, it doesn’t perform as well as .on() (all events are attached to the document element), and doesn’t support chaining.
Ben Plum
Just to prove your first point on multiple element selector strings:
http://jsperf.com/id-vs-class-vs-tag-selectors/12
JulienW
@Andi: I’m quite surprised too. Especially on Chrome.
It means that adding once as innerHTML is always faster than creating individual nodes.
But I’m really not satisfied by this as it makes the code exposed to bad characters in strings (read: code injection).
I’d like to see what happens with DOM document fragments…
Anyway, I dislike to micro-optimize because the measured performance can change in the next browser version… I do prefer to code secure and reasonably fast, than the contrary. If jQuery is your bottleneck, you’re doing something wrong (like putting your model in the DOM).
Matthew
Would it be possible for jQuery to detect when an ID is the first bit of a selector string and silently-internally do a getElementById call followed by a .find with the remaining string? I can’t think of any problems that would rise from this.
Michael Matyus
I agree with ALEŠ ROUBÍČEK, from what I’ve seen it’s better to use the .push/.join method than jQuery.each.
Scott Kosman
Thanks for the clarifications, Mathias. :hidden was a bad example for me to use as the “title” for the pseudo-selectors section because as you correctly state, it doesn’t use qSA. Good call-out on that.
I was totally unaware of Opera’s caching, too – I actually can’t run Opera on my work computer because it can be used to torrent so I can’t do a lot of testing with it!
Amber Weinberg
Great article. I’m guilty of knowing jQuery and not Javascript myself (although I can’t stand either). I didn’t realize the find function was faster than a regular selector. :)
Bart Lewis
In regards to $(’.class .other’) versus $(’.class’).find(’.other’), the docs have this to say:
Internally, selector context is implemented with the .find() method, so $(‘span’, this) is equivalent to $(this).find(‘span’).
Twest
Nice article man, we may be reworking some of our app and the future way we do things because of this read and the new 1.7 on() support
daGrevis
One of the best articles I have read about jQuery so far. Pure awesomeness.
I really would love to see next part of it. More tips – better internet! :D
Bertram Simon
Great article. Thank you.
little performance question:
jquery 1.42 with ~ 70,4 kB and delegate
or
jquery 1.71 with ~ 91,6 kB and on
What would you choose?
Aaron Peterson
A great way to keep track of your cached jquery selectors (I think I picked this up from Alex Sexton) is to prefix the var names with a $. This makes it much easier to remember:
var $elem = $(”#foo”).find(”.bar”);
Joe Larson
Great article and discussion. For my 2cents when I cache jquery objects I usually suffix with $ so I can remember that this is a jquery object (so “block$” instead of “block”). This is especially useful because I frequently grab the DOM node directly to work with it, so this avoids confusion about which thing I’m actually working on. I realize some people may dislike this for all the usual reason any Hungarian-ish notation is disliked, but in this case I find it very helpful.
monkey
Speaking of speed, using jQuery.each to iterate over an array is bloody slow.
Did one of these to test:
http://jsperf.com/array-iteration-jquery-each-vs-for/2
(also a lot of stuff to confirm my thoughts on where var’d stuff went)
That also brought me to trying iterating over a jQuery collection in array style:
http://jsperf.com/looping-through-jquery-objects/2
I wonder how big an impact any of this speed stuff is going to make, though.
Darren
I guess I must be missed something? I always use CSS selectors like this:
$(‘body > section div#content p’)…..
which must let jQuery know not to bother looking anywhere else other than the top lever section element, etc? Surely the same – if not better – than the ‘find’ options being discussed???
But great work!
Scott Kosman
Good question, Darren. Logically you’d think that’s exactly how it works, but it’s actually the opposite. Sizzle (the CSS selector engine built into jQuery) parses selectors from right to left so in your case, it’ll search out all paragraphs, then determine which ones are inside div#content, and so forth.
I knocked together a quick jsperf using your example and comparing it to a couple of different options using .find(), and .find() is significantly faster.
Katie Blackman
This is a great guide for those who don’t get Javascript but understand enough jQuery to make things happen. It’s nice to finally get your jQuery commands working after reading and rereading the docs. But, as we do with our HTML and CSS, there is some clean-up always required. Performance is huge. Eight seconds is all it takes before someone’s clicking off your page onto something more interesting. This article is so helpful and will be tucked away in my bookmarks for when I need it next. Thanks!
Scott Kosman
Hey Jose, sorry for the late reply.
There are a ton of different methods for prepending/appending, and unfortunately the best answer to your question about performance is “it depends.” The point I was really trying to make was that, regardless of what kind of element you’re appending, it’s much more performant to append them all at once rather than appending one element in each iteration of the loop.
I read a fantastic article about this recently that I’m trying to dig up again, it did a bunch of performance testing of various ways of appending using loops (both native for loops and jQuery $.each) and by far the best way was pushing content into an array in each iteration and then using that array to build the appended content after the loop was finished. If I can find it again I’ll post it here. Thanks for the question!
Phil Ricketts
I’m surprised nobody has mentioned jQuery’s selector context?
$mysection = $(”#id”).find(“section”);
jQuery(”[attribute=value]”, $mysection);
Scott Kosman
Context was mentioned in the very first comment and I addressed it (along with a number of other things) in a jsperf a few comments later (here). Or is there something more specific you wanted to discuss regarding it?
Andi Farr
Great article – I know I’ve been guilty of more than one of these before.
Quick question on the first section – is $(”#id”).find(“p”) not equivalent to using the context attribute – $(“p”, “#id”)? They do the same, but I prefer the syntax of the second – no idea which is more performant, though!
Jeffrey Way
Nice article.
…Just as long as people remember that, as long as you’re at least somewhat considerate of the selectors you pass to jQuery, you really don’t need to worry about selector performance too much. jQuery does an excellent job of optimizing as much as possible. But it’s good to understand the basic concepts, like right-left parsing.
Sure – $(‘div’).find(‘p’) may potentially be fractionally faster than $(‘div p’), but I won’t lose sleep if I use the latter. Readability is more important in most cases….or unless there is noticeable slowdown in your page’s performance. :)
Nicolas Chevallier
Thanks for all these tips! They are not valid for JQuery moreover, it also works for Mootools for example.
Spyros Rallis
The best way to speed up jQuery is to not use it at all.
Don’t use jQuery because you are afraid (or maybe bored?) to write some simple JavaScript. Don’t use a 30KB framework in place of 10 lines of JavaScript. <em>Think</em> before coding and don’t cut corners just to cut corners — a little bit more work may be a better solution.
Now, don’t take me wrong — jQuery is a great framework and I love it. But I believe it is being overused.
You wouldn’t need to optimize your jQuery code if you used the framework only where necessary, right?
;-)
Dodfr
Very usefull tips !
An other “pro” when you use the .on(‘click’,‘selector’,function({}) is that if you dynamically add a some element in ‘selector’ scope, then it will automatically be caught by the click event and you will not have to stick it to the event :-)
Very usefull when you add new <li> items for example.
This is some good advice – I’d like to add my stone on the wall. On the last example, tmp += “…” creates an intermediary string on every iteration, resulting in poor performance when dealing with large arrays. You should instead make tmp an array, push the li’s in, and then join(’‘) it.