You are on page 1of 87

jQuery Proven

PERFORMANCE
TIPS & TRICKS

IMAGES COPYRIGHT HASBRO AND TONKA, 1935-2011.

WITH ADDY OSMANI

and Mr. Monopoly

ABOUT ME
JavaScript & UI Developer at Aol

jQuery Core [Bugs/Docs/Learning] teams


SocketStream Core Team Member

Writer [Script Junkie / AddyOsmani.com/.net etc]

We used to give out these awesome


free coasters back in the 90s.

We now create real-time web


frameworks and next-gen platforms.

Enough of that..

LETS START!

WHY DOES PERFORMANCE


MATTER?

Apps should be snappy, not sloppy.


Best practices offer optimal approaches
to solving problems.

If we dont follow them, browsers can end


up having to do more work.

MORE WORK =
MORE MEMORY USE =
SLOWER APPS.

TODAY, ALL OF THESE


SLIDES COME WITH
PERFORMANCE TESTS.
Not just saying X is faster...were proving it too.

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!

Example of test output


Anyone can tell what the fastest and slowest snippets are.

http://jsperf.com/jquery-tree-traversing

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.

PERFORMANCE TIP

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.

MOST POPULAR SITES USING JQUERY ON


THE GOOGLE CDN

Old

Stats from Scott Mitchell

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!

WHY?
Older versions wont 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.

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

1.6.x improvements

.attr() performance improved


http://jsperf.com/attr-vs-attrhooks

.val() faster in 1.6.x


http://jsperf.com/valhooks-vs-val/2

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 youre using and youll be fine

PERFORMANCE TIP

KNOW YOUR SELECTORS

All selectors are not created equally


Just because a selection can be made in

many ways, doesnt mean each selector


is just as performant

Do you know what the fastest to


slowest selectors are?

Fast: ID & Element Selectors


$(#Element, form, input)

ID and element selectors are the fastest


This is because theyre backed by native
DOM operations (eg. getElementById()).

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

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

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 dont such as :first, :last, :eq etc.

Valid selectors have a better chance of using it.

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

BUT IM TOO PRETTY TO GO


TO JAIL!

Pseudo-selectors
are powerful..but
slow, so be careful
when using them.

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?

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!.

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-vssizzle-vs-midori-vs-mootools-selectors-test/26

PERFORMANCE TIP

UNDERSTAND PARENTS
AND CHILDREN
1) $(.child", $parent).show(); //context
2) $parent.find(.child).show(); //find()
3) $parent.children(".child).show(); //immediate children
4) $(#parent > .child).show(); //child combinator selector
5) $(#parent .child).show(); //class selector
6) $('.child', $('#parent')).show(); //created context

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

.find()
2) $parent.find(.child).show();

This is the fastest of the entire set. Ill


explain why shortly.

Immediate children
3) $parent.children(.child).show();

Internally uses $.sibling and JavaScripts

nextSibling() to find nodes following


other nodes in the same tree.

~50% slower than the fastest option

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


its a direct child of the parent.

~70% slower than the fastest option

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

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

The fastest option is..


2) $parent.find(.child).show();

The parent selector is already cached here, so it


doesnt 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.

Its 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


youre trying to achieve.

jsPerf performance tests

context vs. selector vs. selector and .find()


vs. parent/child selector vs. immediate
children: http://jsperf.com/jqueryselectors-context/2

PERFORMANCE TIP

Dont use jQuery unless its


absolutely necessary

Remember its sometimes more

performant to use regular ol JavaScript

jQuery is JavaScript so theres no harm.


How many times have you done this..

Eg. jQuery over-use of attr()


$('a').bind(click, function(){
console.log('You clicked: ' + $(this).attr('id'));
});

jQuerys ID selector only gets to

document.getElementById after parsing


the selector and creating a jQuery object

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 isnt always the best way.

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.

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.

PERFORMANCE TIP

CACHING IS YOUR FRIEND.


var parents =$(.parents), //caching
children = $(.parents).find(.child), //bad
kids = parents.find(.child); //good

Caching just means were storing the


result of a selection for later re-use.

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.

Doing just about anything with the


cached collection.
var foo = $(.item).bind('click', function({
foo.not(this).addClass(bar)
.removeClass(foobar)
.fadeOut(500);
});

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.

PERFORMANCE TIP

CHAINING
var parents =$(.parents).doSomething().doSomethingElse();

Almost all jQuery methods return a jQuery


object and support chaining.

This means after executing a method on a

selection, you can continue executing more.

Less code and its easier to write!

No-chaining vs. chaining


//Without chaining
$(#notification).fadeIn(slow);
$(#notification).addClass(.activeNotification);
$(#notification).css(marginLeft, 50px);
//With chaining
$(#notification).fadeIn(slow)
.addClass(.activeNotification)
.css(marginLeft, 50px);

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.

PERFORMANCE TIP

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)

.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.

Doesnt work with dynamically inserted


elements.

.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)

Cant chain to it, unlike other jQuery


methods.

.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 dont bubble all the way up the DOM


to capture an elements target (unlike .live())

Use when binding the same event handler to


multiple elements

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-vsjquery-delegate/15

PERFORMANCE TIP

THE DOM ISNT A DATABASE

jQuery allows you to treat it like one but it isnt.


Remember each DOM insertion is costly.
This means keep the use of .append
(), .insertBefore(), .insertAfter() etc. to a
minimum.

Its 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.

Tip 1: Better .append() usage

Minimise use by building HTML strings inmemory 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.

Tip 2: Use .detach()

Works great when youre doing heavy


interaction with a node

Allows you to re-insert the node to the


DOM once youre ready

Up to 60% faster than working with


undetached nodes.

.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();
}
});

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 theres overhead creating a jQuery

object and doing data-parsing in the first.

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.

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-jqueryselectiondata/11

Multiple appends vs a single append http://


jsperf.com/string-concat-single-append-vsmultiple-append

PERFORMANCE TIP

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 its not always the most
performant option.

Plugins like Ben Almans $.each2() sometimes


perform better than $.fn.each

AVOID LOOPS IF YOU CAN. HARD, BUT


NESTED DOM SELECTORS MAY PERFORM
BETTER.

Unless absolutely necessary, avoid loops. Theyre


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.

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 youre lucky, but remember there


are alternatives possible.

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 Almans .each2() http://


jsperf.com/jquery-each-vs-quickeach/3

PERFORMANCE TIP

10

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

$.text vs $.fn.text

http://jsperf.com/jquery-text-vs-html/5

Notes:

Not all jQuery methods have their own singlenode 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/

Bonus Tip

KEEP YOUR CODE DRY

Repeating the same code increases the size of


your code-base and reduces productivity

DRY (dont 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.

Lets 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';

Non-DRY code
$('.someCheckbox').click(function(){
if ( this.checked ){
$('#input_carModel').val(defaultSettings.carModel);
$('#input_carYear').val(defaultSettings.carYear);
$('#input_carMiles').val(defaultSettings.carMiles);
$('#input_carTint').val(defaultSettings.carTint);

} else {
$('#input_carModel').val('');
$('#input_carYear').val('');
$('#input_carMiles').val('');
$('#input_carTint').val('');
}
});

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] : '');
});
});

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

THATS IT!
GO BUILD AWESOME THINGS.

You might also like