Upgrade to Pro — share decks privately, control downloads, hide ads and more …

jQuery Proven Performance Tips & Tricks, 2011

Addy Osmani
October 29, 2011

jQuery Proven Performance Tips & Tricks, 2011

This talk is about the importance of performance-testing your JavaScript and jQuery code (and appreciating performance gotchas you can keep in mind when writing code that uses jQuery).

It includes performance tests for many of the tips we regularly suggest users use so you can see first hand just why a particular best practice is worth using.

Addy Osmani

October 29, 2011
Tweet

More Decks by Addy Osmani

Other Decks in Technology

Transcript

  1. jQuery Proven TIPS & TRICKS WITH ADDY OSMANI IMAGES COPYRIGHT

    HASBRO AND TONKA, 1935-2011. PERFORMANCE and Mr. Monopoly
  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. 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.
  4. TODAY, ALL OF THESE SLIDES COME WITH PERFORMANCE TESTS. Not

    just saying X is faster...we’re proving it too.
  5. 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!
  6. 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.
  7. 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.
  8. 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!
  9. 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.
  10. Selector comparisons1.4.2 vs. 1.4.4 vs. 1.6.2 $(’.elem’) $(’.elem’, context); context.find(’.elem’);

    0 27500 55000 82500 110000 1.4.2 1.4.4 1.6.2 http://jsperf.com/jquery-1-4-2-vs-1-6-2-comparisons
  11. 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
  12. 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?
  13. Fast: ID & Element Selectors •ID and element selectors are

    the fastest •This is because they’re backed by native DOM operations (eg. getElementById()). $(‘#Element,  form,  input’)
  14. Slower: Class Selectors •getElementsByClassName() not supported in IE5-8 •Supported in

    FF3+, Safari 4+, Chrome 4+, Opera 10.10+ so faster in these. $(‘.element’) http://www.quirksmode.org/dom/w3c_core.html
  15. Slowest: Pseudo & Attribute Selectors • 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 $(‘:visible,  :hidden’);   $(‘[attribute=value]’);
  16. 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.
  17. jsPerf selector comparison ID Class Descendent tag Attributes Input/form select

    :nth-child 0 75000 150000 225000 300000 1.4.2 1.6.2 http://jsperf.com/dh-jquery-1-4-vs-1-6/6
  18. The :hidden pseudo-selector • Looking at the code, why is

    this bad? 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 ); }; }
  19. 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!.
  20. 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
  21. 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
  22. Context •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 1)  $(‘.child’,  $parent).show();  
  23. .find() •This is the fastest of the entire set. I’ll

    explain why shortly. 2)  $parent.find(‘.child’).show();  
  24. Immediate children •Internally uses $.sibling and JavaScript’s nextSibling() to find

    nodes following other nodes in the same tree. •~50% slower than the fastest option 3)  $parent.children(‘.child’).show();  
  25. CSS child combinator selector •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 4)  $(‘#parent  >  .child’).show();
  26. CSS class selector • 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 5)  $(‘#parent  .child’).show();  
  27. Created context • Equivalent internally to $(‘#parent’).find(‘.child’), however note that

    parent is a jQuery object. • ~23% slower than the fastest option 6)  $(‘.child’,  $(‘#parent’)).show();  
  28. The fastest option is.. • 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. 2)  $parent.find(‘.child’).show();  
  29. 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.
  30. jsPerf performance tests •context vs. selector vs. selector and .find()

    vs. parent/child selector vs. immediate children: http://jsperf.com/jquery- selectors-context/2
  31. 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..
  32. 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
  33. 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.
  34. 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.
  35. 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.
  36. CACHING IS YOUR FRIEND. •Caching just means we’re storing the

    result of a selection for later re-use. var parents = $(‘.parents’), //caching children = $(‘.parents’).find(‘.child’), //bad kids = parents.find(‘.child’); //good
  37. 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.
  38. Doing just about anything with the cached collection. var foo

    = $(‘.item’).bind('click', function({ foo.not(this).addClass(‘bar’) .removeClass(‘foobar’) .fadeOut(500); });
  39. 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.
  40. CHAINING •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 it’s easier to write! var parents = $(‘.parents’).doSomething().doSomethingElse();
  41. 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’);
  42. 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.
  43. 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)
  44. .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.
  45. .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.
  46. .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
  47. 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
  48. 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.
  49. 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.
  50. 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.
  51. 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.
  52. .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(); } });
  53. Tip 3: Better .data() usage •We usually attach data like

    this.. •But this is actually much faster.. $(‘#elem’).data(  key  ,  value  ); $.data(‘#elem’,  key  ,  value); •as there’s overhead creating a jQuery object and doing data-parsing in the first.
  54. 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.
  55. 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
  56. 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
  57. 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.
  58. 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.
  59. 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
  60. Avoid constructing new jQuery objects unless necessary • 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. $(‘a’).map(function(){ return $(this).text();}); Thanks to James Padolsey for this tip
  61. 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/
  62. 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.
  63. 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';
  64. 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(''); } });
  65. 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] : ''); }); });
  66. 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