Performance optimization is a crucial aspect of building ‘snappy’ client-side applications and something which all developers using jQuery should bear in mind. In this talk, we're going to take a look at some of the best practices, tips and tricks for improving the performance of your jQuery code in 2011 with some quick wins and a few new surprises along the way.
2. ABOUT ME
• JavaScript & UI Developer at Aol
• jQuery Core [Bugs/Docs/Learning] teams
• SocketStream Core Team Member
• Writer [Script Junkie / AddyOsmani.com/.net etc]
3. We used to give out these awesome
free coasters back in the 90s.
4. We now create real-time web
frameworks and next-gen platforms.
6. WHY DOES PERFORMANCE
MATTER?
• Apps should be snappy, not sloppy.
• Best practices offer optimal approaches
to solving problems.
• If we don’t follow them, browsers can end
up having to do more work.
8. TODAY, ALL OF THESE
SLIDES COME WITH
PERFORMANCE TESTS.
Not just saying X is faster...we’re proving it too.
9. PERFORMANCE TESTING
• jsPerf.com - a great way to easily create tests
comparing the performance of code snippets
across different browsers
• Makes it simple for anyone to share or modify
tests
• Used by the jQuery project, Yahoo and many
other dev. teams
Thanks to Mathias Bynens for creating it!
10. Example of test output
Anyone can tell what the fastest and slowest snippets are.
http://jsperf.com/jquery-tree-traversing
11. Quick jsPerf tips for beginners
• ops/sec is the number of times a test is
projected to execute in a second
• Tests get repeatedly executed until they reach the
minimum time required to get a percentage
uncertainly
• Results based on ops/sec accounting for margin
of error. The higher ops/sec the better.
13. STAY UP TO DATE!
• Always use the latest version of jQuery
core where possible.
• Remember to regression test your
scripts and plugins before upgrading.
• Current version is 1.6.2 and 1.7 will
probably get released this fall.
14. MOST POPULAR SITES USING JQUERY ON
THE GOOGLE CDN
Old
Stats from Scott Mitchell
15. INTERESTING FACTS
• Performance improvements and new
features usually land in major releases (eg.
1.6/1.x)
• Bug patches and regression fixes land
in 1.x.y releases (eg. 1.6.2)
• Plenty of reasons to upgrade!
16. WHY?
• Older versions won’t offer these instant
performance benefits
• As 47% of the popular sites on the web
use jQuery, changes are heavily tested.
• Upgrading usually a pain-free process.
17. Selector comparisons1.4.2 vs. 1.4.4
vs. 1.6.2
1.4.2 1.4.4 1.6.2
$(’.elem’)
$(’.elem’, context);
context.find(’.elem’);
0 27500 55000 82500 110000
http://jsperf.com/jquery-1-4-2-vs-1-6-2-comparisons
19. Note
• There are certain selectors that are
slower in 1.6.x than they are in 1.4.x
• Be aware of the performance of
selectors you’re using and you’ll be fine
21. KNOW YOUR SELECTORS
• All selectors are not created equally
• Just because a selection can be made in
many ways, doesn’t mean each selector
is just as performant
• Do you know what the fastest to
slowest selectors are?
22. Fast: ID & Element Selectors
$(‘#Element, form, input’)
• ID and element selectors are the fastest
• This is because they’re backed by native
DOM operations (eg. getElementById()).
23. Slower: Class Selectors
$(‘.element’)
• getElementsByClassName() not
supported in IE5-8
• Supported in FF3+, Safari 4+, Chrome
4+, Opera 10.10+ so faster in these.
http://www.quirksmode.org/dom/w3c_core.html
24. Slowest: Pseudo & Attribute
Selectors
$(‘:visible, :hidden’);
$(‘[attribute=value]’);
• This is due to no native calls available that we can
take advantage of.
• querySelector() and querySelectorAll()
help with this in modern browsers.
http://www.quirksmode.org/dom/w3c_core.html
25. querySelectorAll()
• Allows searching the DOM for elems based
on a CSS selector in modern browsers.
• jQuery attempts to use qSA without hitting
Sizzle for queries including $(‘#parent .child’) or
$(‘.parent a[href!=”hello”]’)
• Optimise for selectors that use qSA vs. those
that don’t such as :first, :last, :eq etc.
• Valid selectors have a better chance of using it.
26. jsPerf selector comparison
1.4.2 1.6.2
ID
Class
Descendent tag
Attributes
Input/form select
:nth-child
0 75000 150000 225000 300000
http://jsperf.com/dh-jquery-1-4-vs-1-6/6
27. BUT I’M TOO PRETTY TO GO
TO JAIL!
Pseudo-selectors
are powerful..but
slow, so be careful
when using them.
28. The :hidden pseudo-selector
if ( jQuery.expr && jQuery.expr.filters ) {
jQuery.expr.filters.hidden = function( elem ) {
var width = elem.offsetWidth,
height = elem.offsetHeight;
return (width === 0 && height === 0) ||(!jQuery.support.reliableHiddenOffsets &&
(elem.style.display ||jQuery.css( elem, "display" )) === "none");
};
jQuery.expr.filters.visible = function( elem ) {
return !jQuery.expr.filters.hidden( elem );
};
}
• Looking at the code, why is this bad?
29. Be careful because..
• If you use this with 100 elements, jQuery
calls it 100 times.
• :hidden is powerful but like all pseudos
must be run against all the elements
in your search space.
• If possible, avoid them!.
30. jsPerf performance tests
• jQuery1.4.2 vs 1.6 selector comparison tests
http://jsperf.com/dh-jquery-1-4-vs-1-6/6
• jQuery 1.2.x vs 1.4.x vs. 1.6.x vs. qSA vs. qS vs.
other frameworks http://jsperf.com/jquery-vs-
sizzle-vs-midori-vs-mootools-selectors-test/26
33. Context
1) $(‘.child’, $parent).show();
• Here the scope must be parsed and
translated to $.parent.find(‘child’).show();
causing it to be slower.
• ~5-10% slower than the fastest option
36. CSS child combinator selector
4) $(‘#parent > .child’).show();
• Uses a child combinator selector, however
Sizzle works from right to left.
• Bad as it will match .child before checking
it’s a direct child of the parent.
• ~70% slower than the fastest option
37. CSS class selector
5) $(‘#parent .child’).show();
• Uses a class selector and is constrained by the
same rules as 4).
• Internally also has to translate to using .find()
• ~77% slower than the fastest option
38. Created context
6) $(‘.child’, $(‘#parent’)).show();
• Equivalent internally to $(‘#parent’).find(‘.child’),
however note that parent is a jQuery object.
• ~23% slower than the fastest option
39. The fastest option is..
2) $parent.find(‘.child’).show();
• The parent selector is already cached here, so it
doesn’t need to be refetched from the DOM.
• Without caching this is ~ 16% slower.
• Directly uses native getElementById,
getElementsByName, getElementsByTagName to
search inside the passed context under the hood.
40. It’s worth noting..
• .find() performs a recursive top-down
search of all child and sub-elements
• Other options presented may be more
suitable/performant depending on what
you’re trying to achieve.
41. jsPerf performance tests
• context vs. selector vs. selector and .find()
vs. parent/child selector vs. immediate
children: http://jsperf.com/jquery-
selectors-context/2
43. Don’t use jQuery unless it’s
absolutely necessary
• Remember it’s sometimes more
performant to use regular ol’ JavaScript
• jQuery is JavaScript so there’s no harm.
• How many times have you done this..
44. Eg. jQuery over-use of attr()
$('a').bind(‘click’, function(){
console.log('You clicked: ' + $(this).attr('id'));
});
• jQuery’s ID selector only gets to
document.getElementById after parsing
the selector and creating a jQuery object
45. Why not use the DOM
element itself? This is faster:
$('a').bind(‘click’, function(){
console.log('You clicked: ' + this.id);
});
• Avoid the overhead by remembering the
jQuery-way isn’t always the best way.
46. Quick note:
• this.id and $(this).attr(‘id’) both return the
same value but remember..
• At a lower-level, this.getAttribute(‘id’) is
equivalent to $(this).attr(‘id’);
• However, as the attribute stays up to
date, this.id is still better to use.
47. jsPerf Performance tests
• $(this).attr(‘id’) vs. this.id http://jsperf.com/
el-attr-id-vs-el-id/2
• Using the former is actually 80-95%
slower than directly accessing the
attribute through the DOM element.
49. CACHING IS YOUR FRIEND.
var parents = $(‘.parents’), //caching
children = $(‘.parents’).find(‘.child’), //bad
kids = parents.find(‘.child’); //good
• Caching just means we’re storing the
result of a selection for later re-use.
50. So remember..
• Each $(‘.elem’) will re-run your search
of the DOM and return a new collection
• You can then do anything with the cached
collection.
• Caching will decrease repeat selections.
51. Doing just about anything with the
cached collection.
var foo = $(‘.item’).bind('click', function({
foo.not(this).addClass(‘bar’)
.removeClass(‘foobar’)
.fadeOut(500);
});
52. jsPerf performance tests
• Comparing the performance of cached
selectors vs. repeated element selections
http://jsperf.com/ns-jq-cached
• Uncached selectors in these tests are
anywhere up to 62% slower than their
cached equivalents.
56. jsPerf performance tests
• Chained calls vs. separate calls vs. cached
separate calls http://jsperf.com/jquery-chaining
• Chaining is the fastest followed by cached
separate calls.
58. EVENT DELEGATION
• The idea that you allow events to bubble
up the DOM tree to a parent element.
• Important as it allows you to only bind a
single event handler rather than 100s.
• Works with elements in the DOM at
runtime (and those injected later)
59. .bind()
• Allows you to attach a handler to an event
such as ‘click’, ‘mouseenter’ etc for elements
• With larger sets, the browser has to keep
track of all event handlers and this can take
time to bind.
• Doesn’t work with dynamically inserted
elements.
60. .live()
• Simplest form of supported event delegation
• Allows you to attach a handler to an event for
current and future matches of a selector
• Works best for simple scenarios but has
flaws (has to be at the top of the chain, fails
alongside traversals)
• Can’t chain to it, unlike other jQuery
methods.
61. .delegate()
• Allows you to specify the particular DOM
element would like to bind to when attaching
handlers to selections that match current/future
elems.
• Ensures we don’t bubble all the way up the DOM
to capture an element’s target (unlike .live())
• Use when binding the same event handler to
multiple elements
62. jsPerf performance tests
• .live() vs .delegate() vs. delegate from body variations
http://jsperf.com/jquery-delegate-vs-live-table-test/2
• .bind() vs .click() vs. live() vs. delegate() http://
jsperf.com/bind-vs-click/12
• .live() vs .live() context vs .delegate() vs. delegating to
document.body http://jsperf.com/jquery-live-vs-
jquery-delegate/15
64. THE DOM ISN’T A DATABASE
• jQuery allows you to treat it like one but it isn’t.
• Remember each DOM insertion is costly.
• This means keep the use of .append
(), .insertBefore(), .insertAfter() etc. to a
minimum.
65. It’s also important to remember
• Traversing the DOM to retrieve content or
information stored in .text(), .html() etc is not
the most optimal approach.
• This could be in .data() instead, which allows us
to attach any type of data to DOM elements
safely.
66. Tip 1: Better .append() usage
• Minimise use by building HTML strings in-
memory and using a single .append()
instead.
• Multiple appends can be up to 90%
slower when not appending to cached
selectors and up to 20% slower with them.
67. Tip 2: Use .detach()
• Works great when you’re doing heavy
interaction with a node
• Allows you to re-insert the node to the
DOM once you’re ready
• Up to 60% faster than working with
undetached nodes.
68. .detach() example
$(‘p’).click(function(){
$(this).toggleClass(‘off’);
});
var p;
$(‘button’).click(function(){
if ( p ) {
/*..additional modification*/
p.appendTo(‘body’);
p = null;
} else {
p = $(‘p’).detach();
}
});
69. Tip 3: Better .data() usage
• We usually attach data like this..
$(‘#elem’).data( key , value );
• But this is actually much faster..
$.data(‘#elem’, key , value);
• as there’s overhead creating a jQuery
object and doing data-parsing in the first.
70. Notes
• Although $.data is faster, it cannot be
passed a selector, only a node.
• This means $.data(elem, key, value) works
where elem is already defined as an
element.
71. jsPerf performance tests
• .detach() vs not detaching http://
jsperf.com/to-detach-or-not-to-detach
• jQuery.data vs jQuery.fn.data: http://
jsperf.com/jquery-data-vs-jqueryselection-
data/11
• Multiple appends vs a single append http://
jsperf.com/string-concat-single-append-vs-
multiple-append
73. UNDERSTAND LOOPS
• Did you know that native for and while loops are
faster than using $.each() and $.fn.each()?
• jQuery makes it easy to iterate over collections,
but remember it’s not always the most
performant option.
• Plugins like Ben Alman’s $.each2() sometimes
perform better than $.fn.each
74. AVOID LOOPS IF YOU CAN. HARD, BUT
NESTED DOM SELECTORS MAY PERFORM
BETTER.
• Unless absolutely necessary, avoid loops. They’re
slow in every programming language.
• If possible, use the selector engine instead to
access the elements needed.
• There are of course places loops cannot be
substituted but try your best to optimise.
75. That said..
• Developers often need to iterate
• The closure-scope provided by $.each is usually
required for other reasons.
• Should loops be such a pain-point you need to
unroll them you’re lucky, but remember there
are alternatives possible.
76. jsPerf performance tests
• jQuery.each vs. for, while, reverse for,
jQuery.fn.each and other loop approaches: http://
jsperf.com/jquery-each-vs-for-loop/24
• jQuery.fn.each vs Ben Alman’s .each2() http://
jsperf.com/jquery-each-vs-quickeach/3
78. Avoid constructing new jQuery objects unless
necessary
$(‘a’).map(function(){ return $(this).text();});
• Developers commonly create new jQuery
objects on iterations such as the above just to
access some text
• Using a lower-level method like $.method()
rather than $.fn.method() can help improve
performance with this.
Thanks to James Padolsey for this tip
80. Notes:
• Not all jQuery methods have their own single-
node functions
• James proposed jQuery.single() as a solution to
this problem
• It uses a single jQuery object for all calls to
jQuery.single() and only works for single DOM
elements.
http://james.padolsey.com/javascript/76-bytes-for-faster-jquery/
82. KEEP YOUR CODE DRY
• Repeating the same code increases the size of
your code-base and reduces productivity
• DRY (don’t repeat yourself) encourages one
representation of each piece of knowledge
• Keeping code minimal can also remind you
that chaining, caching etc can assist with this.
83. Let’s go through a quick example..
/*Let's store some default values to be read later*/
var defaultSettings = {};
defaultSettings['carModel'] = 'Mercedes';
defaultSettings['carYear’] = 2012;
defaultSettings['carMiles'] = 5000;
defaultSettings['carTint'] = 'Metallic Blue';
85. DRY code
var props = ['carModel', 'carYear', 'carMiles', 'carTint'];
$('.someCheckbox').click(function(){
var checked = this.checked;
/*
What are we repeating?
1. input_ precedes each field name
2. accessing the same array for settings
3. repeating value resets
What can we do?
1. programmatically generate the field names
2. access array by key
3. merge this call using terse coding (ie. if checked,
set a value, otherwise don't)
*/
$.each(props,function(i,key){
$('#input_' + key).val(checked ? defaultSettings[key] : '');
});
});
86. THANKS.
• Props to Adam Sontag, JD Dalton, Paul Irish,
Timmy Willison, James Padolsey, Mathias
Bynens, Matt Baker and the team @jquery
• For more on me:
• http://addyosmani.com
• @addyosmani