SlideShare a Scribd company logo
JavaScript: An Experiment January 13, 2010 Willi Gamboa [email_address]
Objective Find out how expensive interactions between JavaScript and the DOM are, as compared to operations that are purely in JavaScript
Hypothesis If you are going to perform complex manipulations on an element, it would be more efficient to: Detach the element from the DOM first  or create a new element outside of the DOM Perform the complex manipulations on the element Reattach/attach the element to the DOM
Document Fragments Similar in concept to the hypothesis If a document fragment is appended to an element that is part of the DOM, all the child nodes of the fragment will be moved to the child list of the element  natively var fragment = document.createDocumentFragment (); See  John  Resig’s  demo
Use Case: GOLF Leaderboard
Use Case: GOLF Leaderboard Sorting/resorting is good A div object is created and filled outside of the DOM, after which it is appended to the DOM Creating/refreshing the leaderboard is good, but could be slightly optimized The leaderboard’s three sections are appended to the DOM separately Make only one append to the DOM instead
Procedure Create a  mini-application  to simulate the two cases: Case 1 Element stays attached to the DOM all throughout the process Case 2 Element is detached from the DOM, then reattached
Examine the Code Some best practices and tidbits along the way View the  script source
Preserve the Namespace Enclose code in a self-executing anonymous function: (function ($) { /* Code goes here */ var ul = $ (' #ul '); }) (jQuery); The jQuery object is passed to the $ parameter This inner $ will protect from namespace conflict if $ has already been defined globally by some other code
Scoping (function ($) { var localVariable = ' is in the local scope of the anonymous function because of the var declaration '; window.globalVariable = ' is in the global scope, which is the same as the window scope '; }) (jQuery);
The Application Singleton A singleton is an object that: Can only have one instance Is defined with an object literal, the same way as JSON window.domTest = { defaultSettings : {}, init : function (settings) {}, startTest : function (remove) {}, process : function (remove) {} };
Watch out for This window.domTest = { defaultSettings : {}, init : function (settings) {}, startTest : function (remove) {}, process : function (remove) {} }; Instant IE Death
Objects with Privacy Aka, the Module Used by the Rails team for polls var module = function () { var privateProperty = ' secret '; var publicProperty = ' not secret '; var privateMethod = function () {}; var publicMethod = function () {}; return { publicProperty : publicProperty, publicMethod : publicMethod } };
Objects with Privacy var instance = module (); alert (instance.publicProperty)     not secret alert (instance.publicMethod)     function () {} alert (instance.privateProperty)     instance.privateProperty is undefined alert (instance.privateMethod)     instance.privateMethod is undefined
Settings/Parameters Defining settings as JSON makes it clear what the parameter names are Parameter order is  not important defaultSettings :  { iterations :   30 , liAmount :  50 , colorArray :  ' red orange yellow green blue indigo violet '.split (' '), liTemplate :  ' <li><a href=&quot;#&quot;><strong>This</strong> is a <em>line</em> of <u>text</u> with a #{color} background color.</a></li> ', colorPattern :  new RegExp (' #{.*?} ', ' g ') },
Extending Objects init : function (currentSettings) { this.settings = $.extend ({}, this.defaultSettings, currentSettings); … }, Merge  currentSettings  over  this.defaultSettings  without changing the values of  this.defaultSettings If  currentSettings  is undefined, jQuery makes  this.settings = this.defaultSettings
Array Definition Trick It’s easier to define an array this way: colorArray : ' red orange yellow green blue indigo violet '.split (' ') Instead of: colorArray : new Array (' red ', ' orange ', ' yellow ', ' green ', ' blue ', ' indigo ', ' violet ')
The live () Event Listener /* This event listener is declared as the DOM is loading */ $ (' .button ').live (' click ', function (event) { switch ($ (this).attr (' rel ')) { case ' Test 1 ': domTest.startTest (false); break; }; event.preventDefault (); });
How live () works Event delegation Event bubbling Listen for an event at a higher level in the ancestor tree jQuery’s  live ()  places its event listener on the  document  object, which is always present This means that  live ()  and its unbinder,  die ()  don’t have to wait for the  $ (document).ready  event to work correctly
How live () works Take <ul id=&quot;ul&quot;> <li>1</li> <li>2</li> <li>3</li> </ul> <input id=&quot;button&quot; type=&quot;button&quot; value=&quot;Add li&quot; />
How live () works var addListener = function (element, eventType, handler) { if (element.addEventListener) { element.addEventListener (eventType, handler, false); } else if (element.attachEvent) { element.attachEvent (' on ' + eventType, handler); } else { element [' on ' + eventType] = handler; } };
How live () works addListener (window, 'load', function () { var ul = document.getElementById ('ul'); addListener (ul, ' mouseover ', function (event) { var eventSource = event.target ? event.target : event.srcElement; if (eventSource.nodeName.toLowerCase () == ' li ') { eventSource.style.backgroundColor = ' red '; } }); addListener (document.getElementById ('button'), 'click', function () { ul.appendChild (document.createElement ('li')); }); });
How live () works Test it  in Firefox and IE
How live () works Caveat:  Take <div> <ul id=&quot;ul&quot;> <li>1</li> <li>2</li> <li>3</li> </ul> </div> where  “live”  is applied to the  <div>  instead of the  <ul>  for a  mouseover  event If  <ul>  has a separate  mouseover  listener that returns  false , no  <li>   mouseover  event will reach the  “live”  listener on the  <div> “ live”  won’t work in this case
The Element Manipulations process : function (remove) { if (remove) { domTest.ul.remove (); } for (var i =  0 ; i < this.settings.liAmount; i++) { var randomIndex = Math.round (Math.random () * this.colorIndexCount); var color = this.settings.colorArray [randomIndex]; var li = $ (this.settings.liTemplate.replace (this.settings.colorPattern, color)); li.appendTo (domTest.ul); var anchor = li.find (' a '); anchor.css (' color ', ' #fff '); anchor.css (' font-size ', ' 18px '); anchor.css (' text-decoration ', ' none '); li.data (' color ', color); } if (remove) { domTest.body.append (domTest.ul); } }
Observe the Results Get the average and aggregate completion times of  process () Qualification: If the  maximum completion time  of a sample is inordinately greater than the  average completion time  (e.g.,  500 ms  vs.  38 ms ), then the results for that run are  invalid
Conclusion Do the results prove the hypothesis? Just how expensive are interactions between JavaScript and the DOM? Page load delay: a comparable frame of reference? Amazon:  100 millisecond delay = loss of 1% in sales Yahoo:  400 millisecond delay = loss of 5-9% in full page traffic Google:  500 millisecond delay = 20% fewer searches GOLF leaderboard:  JavaScript refreshes can be considered new page loads

More Related Content

Javascript Experiment

  • 1. JavaScript: An Experiment January 13, 2010 Willi Gamboa [email_address]
  • 2. Objective Find out how expensive interactions between JavaScript and the DOM are, as compared to operations that are purely in JavaScript
  • 3. Hypothesis If you are going to perform complex manipulations on an element, it would be more efficient to: Detach the element from the DOM first or create a new element outside of the DOM Perform the complex manipulations on the element Reattach/attach the element to the DOM
  • 4. Document Fragments Similar in concept to the hypothesis If a document fragment is appended to an element that is part of the DOM, all the child nodes of the fragment will be moved to the child list of the element natively var fragment = document.createDocumentFragment (); See John Resig’s demo
  • 5. Use Case: GOLF Leaderboard
  • 6. Use Case: GOLF Leaderboard Sorting/resorting is good A div object is created and filled outside of the DOM, after which it is appended to the DOM Creating/refreshing the leaderboard is good, but could be slightly optimized The leaderboard’s three sections are appended to the DOM separately Make only one append to the DOM instead
  • 7. Procedure Create a mini-application to simulate the two cases: Case 1 Element stays attached to the DOM all throughout the process Case 2 Element is detached from the DOM, then reattached
  • 8. Examine the Code Some best practices and tidbits along the way View the script source
  • 9. Preserve the Namespace Enclose code in a self-executing anonymous function: (function ($) { /* Code goes here */ var ul = $ (' #ul '); }) (jQuery); The jQuery object is passed to the $ parameter This inner $ will protect from namespace conflict if $ has already been defined globally by some other code
  • 10. Scoping (function ($) { var localVariable = ' is in the local scope of the anonymous function because of the var declaration '; window.globalVariable = ' is in the global scope, which is the same as the window scope '; }) (jQuery);
  • 11. The Application Singleton A singleton is an object that: Can only have one instance Is defined with an object literal, the same way as JSON window.domTest = { defaultSettings : {}, init : function (settings) {}, startTest : function (remove) {}, process : function (remove) {} };
  • 12. Watch out for This window.domTest = { defaultSettings : {}, init : function (settings) {}, startTest : function (remove) {}, process : function (remove) {} }; Instant IE Death
  • 13. Objects with Privacy Aka, the Module Used by the Rails team for polls var module = function () { var privateProperty = ' secret '; var publicProperty = ' not secret '; var privateMethod = function () {}; var publicMethod = function () {}; return { publicProperty : publicProperty, publicMethod : publicMethod } };
  • 14. Objects with Privacy var instance = module (); alert (instance.publicProperty)  not secret alert (instance.publicMethod)  function () {} alert (instance.privateProperty)  instance.privateProperty is undefined alert (instance.privateMethod)  instance.privateMethod is undefined
  • 15. Settings/Parameters Defining settings as JSON makes it clear what the parameter names are Parameter order is not important defaultSettings : { iterations : 30 , liAmount : 50 , colorArray : ' red orange yellow green blue indigo violet '.split (' '), liTemplate : ' <li><a href=&quot;#&quot;><strong>This</strong> is a <em>line</em> of <u>text</u> with a #{color} background color.</a></li> ', colorPattern : new RegExp (' #{.*?} ', ' g ') },
  • 16. Extending Objects init : function (currentSettings) { this.settings = $.extend ({}, this.defaultSettings, currentSettings); … }, Merge currentSettings over this.defaultSettings without changing the values of this.defaultSettings If currentSettings is undefined, jQuery makes this.settings = this.defaultSettings
  • 17. Array Definition Trick It’s easier to define an array this way: colorArray : ' red orange yellow green blue indigo violet '.split (' ') Instead of: colorArray : new Array (' red ', ' orange ', ' yellow ', ' green ', ' blue ', ' indigo ', ' violet ')
  • 18. The live () Event Listener /* This event listener is declared as the DOM is loading */ $ (' .button ').live (' click ', function (event) { switch ($ (this).attr (' rel ')) { case ' Test 1 ': domTest.startTest (false); break; }; event.preventDefault (); });
  • 19. How live () works Event delegation Event bubbling Listen for an event at a higher level in the ancestor tree jQuery’s live () places its event listener on the document object, which is always present This means that live () and its unbinder, die () don’t have to wait for the $ (document).ready event to work correctly
  • 20. How live () works Take <ul id=&quot;ul&quot;> <li>1</li> <li>2</li> <li>3</li> </ul> <input id=&quot;button&quot; type=&quot;button&quot; value=&quot;Add li&quot; />
  • 21. How live () works var addListener = function (element, eventType, handler) { if (element.addEventListener) { element.addEventListener (eventType, handler, false); } else if (element.attachEvent) { element.attachEvent (' on ' + eventType, handler); } else { element [' on ' + eventType] = handler; } };
  • 22. How live () works addListener (window, 'load', function () { var ul = document.getElementById ('ul'); addListener (ul, ' mouseover ', function (event) { var eventSource = event.target ? event.target : event.srcElement; if (eventSource.nodeName.toLowerCase () == ' li ') { eventSource.style.backgroundColor = ' red '; } }); addListener (document.getElementById ('button'), 'click', function () { ul.appendChild (document.createElement ('li')); }); });
  • 23. How live () works Test it in Firefox and IE
  • 24. How live () works Caveat: Take <div> <ul id=&quot;ul&quot;> <li>1</li> <li>2</li> <li>3</li> </ul> </div> where “live” is applied to the <div> instead of the <ul> for a mouseover event If <ul> has a separate mouseover listener that returns false , no <li> mouseover event will reach the “live” listener on the <div> “ live” won’t work in this case
  • 25. The Element Manipulations process : function (remove) { if (remove) { domTest.ul.remove (); } for (var i = 0 ; i < this.settings.liAmount; i++) { var randomIndex = Math.round (Math.random () * this.colorIndexCount); var color = this.settings.colorArray [randomIndex]; var li = $ (this.settings.liTemplate.replace (this.settings.colorPattern, color)); li.appendTo (domTest.ul); var anchor = li.find (' a '); anchor.css (' color ', ' #fff '); anchor.css (' font-size ', ' 18px '); anchor.css (' text-decoration ', ' none '); li.data (' color ', color); } if (remove) { domTest.body.append (domTest.ul); } }
  • 26. Observe the Results Get the average and aggregate completion times of process () Qualification: If the maximum completion time of a sample is inordinately greater than the average completion time (e.g., 500 ms vs. 38 ms ), then the results for that run are invalid
  • 27. Conclusion Do the results prove the hypothesis? Just how expensive are interactions between JavaScript and the DOM? Page load delay: a comparable frame of reference? Amazon: 100 millisecond delay = loss of 1% in sales Yahoo: 400 millisecond delay = loss of 5-9% in full page traffic Google: 500 millisecond delay = 20% fewer searches GOLF leaderboard: JavaScript refreshes can be considered new page loads