Beyond DOMReady: Ultra High-Performance Javascript
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Beyond DOMReady: Ultra High-Performance Javascript

on

  • 2,450 views

Leverage patterns of large-scale JS – such as modules, publish-subscribe and delegation – to achieve extreme performance without sacrificing maintainability.

Leverage patterns of large-scale JS – such as modules, publish-subscribe and delegation – to achieve extreme performance without sacrificing maintainability.

Statistics

Views

Total Views
2,450
Views on SlideShare
2,418
Embed Views
32

Actions

Likes
4
Downloads
31
Comments
0

2 Embeds 32

http://www.linkedin.com 27
https://www.linkedin.com 5

Accessibility

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Beyond DOMReady: Ultra High-Performance Javascript Presentation Transcript

  • 1. this is not a TEDTALK
  • 2. not high-performance javascript
  • 3. ultra high-performance javascript
  • 4. what is ultra high-performance?
  • 5. made possible via• fast file loading.• small file sizes.• avoiding DOM bottlenecks.
  • 6. what high-performance looks like
  • 7. hint: in general it looks awful
  • 8. high-performance != maintainabilitythe elements of high-performance are usuallyat odds with best practices in maintainability…
  • 9. an approachstart with maintainability, and achieve high-performance by building it in via automatedprocesses.
  • 10. large-scale JSto write maintainable code, look at the patternsused by other large-scale JS frameworks:
  • 11. large-scale JS• separate code into modules, each of which accomplishes a single function.• expose the module through an interface.
  • 12. modularprogramming
  • 13. module patternmodule consists of 3 parts:
  • 14. module patternmodule consists of 3 parts:1. function (what it does)
  • 15. var _transform = function(sel) { $(sel).toggleClass(robot);}
  • 16. // wrapped in a self-executing functionvar transformer = function() { var _transform = function(sel) { $(sel).toggleClass(robot); }}();
  • 17. module patternmodule consists of 3 parts:1. function (what it does)2. dependencies (what it needs)
  • 18. var transformer = function($) { var _transform = function(sel) { $(sel).toggleClass(robot); }}(jQuery);
  • 19. module patternmodule consists of 3 parts:1. function (what it does)2. dependencies (what it needs)3. interface (what it returns)
  • 20. var transformer = function($) { var _transform = function(sel) { $(sel).toggleClass(robot); } // sets what `transformer` is equal to return = { transform: _transform }}(jQuery);
  • 21. var transformer = function($) { var _transform = function(sel) { $(sel).toggleClass(robot); } // sets what `transformer` is equal to return = { transform: _transform }}(jQuery);// usagetransformer.transform(.car);
  • 22. var transformer = function($) { var _transform = function(sel) { $(sel).toggleClass(robot); } // sets what `transformer` is equal to return = { transform: _transform }}(jQuery);// usagetransformer.transform(.car);// result<div class="car robot" />
  • 23. benefits of modular programming• self contained – includes everything it needs to accomplish its function.• namespaced – doesnt dirty the global scope.
  • 24. // you can’t do this… yetimport "transformer.js" as transformer;
  • 25. 3rd party loaders
  • 26. 3rd party loaders• LABjs• HeadJS• ControlJS• RequireJS• Load.js• YepNope.js• $script.js
  • 27. 3rd party loaders• LABjs • LazyLoad• HeadJS • curl.js• ControlJS • JsDefer• RequireJS • jquery.defer.js• Load.js • BravoJS• YepNope.js • JSLoad• $script.js • StealJS
  • 28. 3rd party loaders• LABjs • LazyLoad• HeadJS • curl.js• ControlJS • JsDefer• RequireJS • jquery.defer.js• Load.js • BravoJS• YepNope.js • JSLoad• $script.js • StealJS …and more
  • 29. I’ll make it easy…
  • 30. I’ll make it easy…just use RequireJS.
  • 31. I’ll make it easy…just use RequireJS.• plugin architecture (text, l10n, css, etc).• built in support for has.js.• support for r.js.• James Burke knows his shit.• author of the AMD standard.
  • 32. // vanilla js modulevar transformer = function($) { var _transform = function(sel) { $(sel).toggleClass(robot); } return = { transform: _transform }}(jQuery);
  • 33. // AMD module wraps everything in `define`define(function($) { var _transform = function(sel) { $(sel).toggleClass(robot); } return = { transform: _transform }}(jQuery));
  • 34. // dependency array is the first parameter of define// dependencies mapped to parameters in the callbackdefine([ jquery], function($) { var _transform = function(sel) { $(sel).toggleClass(robot); } return = { transform: _transform }});
  • 35. // dependency array is the first parameter of define// dependencies mapped to parameters in the callbackdefine([ jquery, underscore], function($, _) { var _transform = function(sel) { $(sel).toggleClass(robot); } return = { transform: _transform }});
  • 36. // usagerequire([ transformer], function(transformer) { transformer.transform(.car);});
  • 37. example website• common.js – code shared by all pages.• home/main.js – code unique to the home page.
  • 38. // common.jsdefine([ jquery, ui/jquery.ui.core, ui/jquery.ui.widget], function($) { // setup code for all pages});
  • 39. // common.jsdefine([ jquery, ui/jquery.ui.core, ui/jquery.ui.widget], function($) { // setup code for all pages});// but `jquery.ui.core` and `jquery.ui.widget`// aren’t modules!
  • 40. // common.jsrequirejs.config({ shim: { ui/jquery.ui.core: { deps: [jquery] }, ui/jquery.ui.widget: { deps: [ui/jquery.ui.core] } }});define([ jquery, ui/jquery.ui.core, ui/jquery.ui.widget], function($) {
  • 41. // home/main.jsdefine([ common, ui/jquery.ui.dialog], function($) { $(.modal).dialog();});
  • 42. // home/main.jsdefine([ common, ui/jquery.ui.dialog], function($) { $(.modal).dialog();});// index.html<script src="require.js" data-main="home/main"></script>
  • 43. // home/main.jsdefine([ common, ui/jquery.ui.dialog], function($) { $(.modal).dialog();});// index.html<script src="require.js" data-main="<?php echo $template?>/main"></script>
  • 44. behind the scenes (phase 1)1. download require.js – 1.2. download home/main.js – 2.3. check dependencies.4. download common.js, ui/jquery.ui.dialog – 4.5. check dependencies.6. download jquery, ui/jquery.ui.core, ui/jquery.ui.widget – 7.7. check dependencies.
  • 45. behind the scenes (phase 2)1. evaluate jquery, then jquery.ui.core, then jquery.ui.widget.2. execute the common.js callback.3. evaluate jquery.ui.dialog.4. execute the home/main.js callback.
  • 46. modular and maintainablebut crappy performance: 7 requests!
  • 47. make it high-performance
  • 48. introducing r.jsmodule optimizer.
  • 49. // build.js({ modules: [ { name: "common" }, { name: "home/main" exclude: "common" } ]})
  • 50. // run it manually// or as part of automated build processjava -classpath r.js/lib/rhino/js.jar org.mozilla.javascript.tools.shell.Main r.js/dist/r.js -o build.js
  • 51. // example outputTracing dependencies for: commoncommon.js----------------jquery.jsui/jquery.ui.coreui/jquery.ui.widgetcommon.js
  • 52. // example outputTracing dependencies for: home/mainhome/main.js----------------ui/jquery.ui.dialoghome/main.js
  • 53. // example outputUglifying file: common.jsUglifying file: home/main.js
  • 54. new behind the scenes (phase 1)1. download require.js – 1.2. download home/main.js (includes ui/jquery.ui.dialog) – 2.3. check dependencies.4. download common.js (includes jquery, ui/jquery.ui.core, ui/jquery.ui.widget) – 3.5. check dependencies.
  • 55. only 3 requests!• only 1 request per page after initial page load (require.js and common.js are cached for all pages).• scripts loads asynchronously (non-blocking) and in parallel.• all assets optimized (supports uglify or closure compiler).
  • 56. mandatory builds for UI sucks
  • 57. // build.js({ baseUrl: "js-src/", // input folder dir: "js/", // output folder modules: [ { name: "common" }, { name: "home/main" exclude: "common" } ]})
  • 58. // index.html<script src="js/require.js" data-main="<?php echo $_GET[dev] ? js-src/home/main : js/home/main ?>"></script>// index.html – production js, 3 requests// index.html?dev - development js, 7 requests
  • 59. even better performance with has.jsfeature detection library.
  • 60. define([ has], function($) { // add a test var re = /bdevb/; has.add(dev,re.test(window.location.search)); // use `has` if (has(dev)) { console.log(test); }});
  • 61. define([ has], function($) { // add a test var re = /bdevb/; has.add(dev,re.test(window.location.search)); // use `has` if (has(dev)) { console.log(test); }});// index.html?dev// "test"
  • 62. // build.js({ baseUrl: "js-src/", dir: "js/", has: { dev: false }, modules: [ … ]})
  • 63. // originalif (has(dev)) { console.log(test);}
  • 64. // originalif (has(dev)) { console.log(test);}// after r.js pre-processingif (false) { console.log(test);}
  • 65. // originalif (has(dev)) { console.log(test);}// after r.js pre-processingif (false) { console.log(test);}// after uglify post-processing// nothing – uglify strips dead code branches
  • 66. has.add(ie7-support, true);if (has(ie7-support) { // some godawful hack to fix something in ie7}
  • 67. make it ultra high-performance
  • 68. even better performance with almondintended for single page apps or mobile whererequest latency is much worse than desktop.• require.js = 16.5k minified (6k gzipped)• almond.js = 2.3k minified (~1k gzipped)
  • 69. only 1 request… ever.• shaves 14k of boilerplate.
  • 70. 1st step to ultra high performanceuse modular programming.• combine with require.js for asynchronous / parallel loading.• automatic concatenation, optimization.• for ultra performance use almond.js.
  • 71. anyone not use jquery?“Study shows half of all websites use jQuery” – August, 2012
  • 72. // example of a jquery plugin used with a moduledefine([ jquery, jquery.craftyslide], function($) { $(#slideshow).craftyslide();});
  • 73. // closer look at `craftyslide.js`$.fn.craftyslide = function (options) { function paginate() { … } function captions() { … } function manual() { … } paginate(); captions(); manual();}
  • 74. problem with jquery pluginsthey’re a black box.• not easily extendable.• not easily testable.
  • 75. problem with jquery pluginsthey’re a black box.• not easily extendable.• not easily testable.jquery ui set out to solve this with…
  • 76. uiwidgets
  • 77. oh noes! not jquery uibloated piece of crap (210k omg!)• jquery ui is modular – use just the bits you need.• ui core + ui widget + effects core (16k minified or ~6k gzipped).
  • 78. ui widgetsthe two things plugins suck at, widgets doreally well:• theyre fully extendable.
  • 79. simple javascript inheritence25 lines of javascript sexiness:• constructors.• object-oriented inheritence.• access to overridden (super) methods.
  • 80. simple javascript inheritence25 lines of javascript sexiness:• constructors.• object-oriented inheritence.• access to overridden (super) methods.also the foundation of ui widget extensibility.
  • 81. // example widget$.widget(ui.transformer, { options: { … }, _create: function() { … });
  • 82. // example widget$.widget(ui.transformer, { options: { … }, _create: function() { … });// extending it$.widget(ui.autobot, $.ui.transformer, { // extend anything or everything});
  • 83. not-so simple javascript inheritenceeverything from simple javascript inheritence,plus:• namespaces.• public and private methods.• getters/setters.• disable/enable.
  • 84. ui widgetsthe two things plugins suck at, widgets doreally well:• theyre fully extendable.• theyre tuned for testing.
  • 85. // if `craftyslide` were a widget$.widget(ui.craftyslide, { _create: function() { … this._paginate(); this._captions(); this._manual(); }, _paginate: function(){ … }, _captions: function(){ … }, _manual: function(){ … });
  • 86. // adding triggers as hooks for testing$.widget(ui.craftyslide, { … _paginate: function(){ this._trigger(beforePaginate); … this._trigger(afterPaginate); }, …);
  • 87. // in your unit testfunction beforePaginate() { // test conditions}function afterPaginate() { // test conditions}$(#slideshow).craftyslide({ beforePaginate: beforePaginate, afterPaginate: afterPaginate});
  • 88. // plugin using `.on()`function manual() { … $pagination.on(click, function (e) { … });}
  • 89. // plugin using `.on()`function manual() { … $pagination.on(click, function (e) { … });}// widget using `._on()`manual: function() { this._on($pagination, { click: _click }}
  • 90. // `._on()` remembers all event bindings_on: function( element, handlers ) { … this.bindings = this.bindings.add( element );},
  • 91. // `._on()` remembers all event bindings_on: function( element, handlers ) { … this.bindings = this.bindings.add( element );},// `.remove()` triggers a `remove` eventthis._on({ remove: "destroy" });
  • 92. // `._on()` remembers all event bindings_on: function( element, handlers ) { … this.bindings = this.bindings.add( element );},// `.remove()` triggers a `remove` eventthis._on({ remove: "destroy" });// `.destroy()` cleans up all bindings// leaving the DOM pristinedestroy: function() { … this.bindings.unbind( this.eventNamespace );}
  • 93. // setup widget$(#slideshow).craftyslide();// run tests…// teardown// calls `.destroy()`// which automatically unbinds all bindings$(#slideshow).remove();
  • 94. high-performance from code re-use
  • 95. define([ jquery, ui/jquery.ui.core, ui/jquery.ui.widget, ui/jquery.ui.craftyslide], function($) { $.widget(ui.craftyslide, $.ui.craftyslide, { _manual: function() { // extend to do whatever I want } });});
  • 96. 2nd step to ultra high performanceuse object-oriented widgets as code buildingblocks.• inheritance promotes code re-use, smaller codebase.• built on an architecture that promotes testability.
  • 97. made possible via• fast file loading.• small file sizes.• avoiding DOM bottlenecks.
  • 98. avoiding DOM bottlenecks
  • 99. eventdelegation
  • 100. <ul id="transformers"> <li><a>Bumblebee</a></li> <li><a>Ratchet</a></li> <li><a>Ironhide</a></li></ul>// typical event binding$(#transformers a).on(click, function() { // do something});
  • 101. <ul id="transformers"> <li><a>Bumblebee</a></li> <li><a>Ratchet</a></li> <li><a>Ironhide</a></li></ul>// typical event binding$(#transformers a).on(click, function() { // do something});// event bubbling allows us to do this$(#transformers).on(click, function() { // do something});
  • 102. // event delegation is similar$(#transformers).on(click, a, function() { // do something});
  • 103. // event delegation is similar$(#transformers).on(click, a, function() { // do something});// but allows us to do this$(document).on(click, #transformers a, function() // do something});
  • 104. why does that kick ass?• more performant – less memory, faster to bind/unbind.• less maintenance – you can add/remove <ul id="transformers"> at any point in time and dont need to re-attach the event listener.• faster – you can bind the event listener to document as soon as the javascript has loaded, you dont need to wait for domready.
  • 105. how does this work with widgets?it doesnt – widgets pitfall is they are a DOMbottleneck.
  • 106. // example `lightbox` widget$(#gallery a).lightbox();// widget depends on `this.element`$.widget(ui.lightbox, { _create: function() { this._on(this.element, { click: show }); }});
  • 107. two workarounds• one for legacy widgets.• better approach for new widgets.
  • 108. // legacy widgets$(document).on(click, #gallery a, function() { $(this) .lightbox() .lightbox(show);});
  • 109. // new widgets$.widget(ui.lightbox, { _create: function() { var sel = this.options.selector; var handler = {}; handler[click + sel] = show’; this._on(handler); }});
  • 110. // new widgets$.widget(ui.lightbox, { _create: function() { var sel = this.options.selector; var handler = {}; handler[click + sel] = show’; this._on(handler); }});// always instantiate on the document$(document).lightbox({ selector: #gallery a});
  • 111. 3rd step to ultra high performancedelegate anything and everything you can.• will add interaction to elements that are lazy-loaded, inserted via ajax after page load, etc.• allows for interaction before domready!
  • 112. delegation isn’t a cure alldelegation works great when the widgetdoesnt need to know about the user up untilthe user interacts with it.but what about widgets that need to affect theDOM on instantiation…
  • 113. how we’ve done this previously• document.load – the 80s of the internet.• document.DOMContentLoaded – the new load event!
  • 114. domready considered an anti-pattern“the short story is that we dont want towait for DOMContentReady (or worse theload event) since it leads to bad userexperience. the UI is not responsive untilall the DOM has been loaded from thenetwork. so the preferred way is to useinline scripts as soon as possible” – Google Closure team
  • 115. <ul id="transformers"> <li><a>Bumblebee</a></li> <li><a>Ratchet</a></li> <li><a>Ironside</a></li></ul><script> $(#transformers).slideshow();</script>
  • 116. oh no you didn’ta problem with our modular approach:• nothing is exposed to the global scope – you cant use modules from the DOM.
  • 117. mediator pattern to the rescuea central point of control that modulescommunicate through – instead ofdirectly with each other.
  • 118. pubsub
  • 119. central point of control• publish• subscribe• unsubscribe
  • 120. it’s so easy• publish = $.trigger• subscribe = $.on• unsubscribe = $.off
  • 121. // in codevar proxy = $({});window.publish = function() { proxy.trigger.apply(proxy, arguments);}window.subscribe = function() { proxy.on.apply(proxy, arguments);}window.unsubcribe = function() { proxy.off.apply(proxy, arguments);}
  • 122. <ul id="transformers"> <li><a>Bumblebee</a></li> <li><a>Ratchet</a></li> <li><a>Ironside</a></li></ul><script> publish(load.transformers);</script>
  • 123. define([ main], function() { subscribe(load.transformers, function() { $(#transformers).slideshow(); });});
  • 124. oh no you didnttwo problems with our modular approach:• nothing is exposed to the global scope – you cant use modules from the DOM.• if the JS is loaded asynchronously you dont know that its available when the browser is parsing the HTML.
  • 125. <head>// blocking, should be tiny (1k) or inlined!<script src="bootstrap.js"></script>// asynchronous non-blocking<script src="require.js" data-main="home/main"></script>
  • 126. // bootstrap.js// needs to be some global object// but we can clean it up afterwardsdocument.queue = [];window.publish = function() { document.queue.push(arguments);}
  • 127. <script> publish(load.transformers);</script>// document.queue = [[load.transformers]]
  • 128. // main.jsdefine([ jquery], function($) { var proxy = $({}); window.publish = function() { proxy.trigger.apply(proxy, arguments); } window.unsubcribe = function() { proxy.off.apply(proxy, arguments); } …
  • 129. window.subscribe = function(event) { proxy.on.apply(proxy, arguments);});
  • 130. window.subscribe = function(event) { proxy.on.apply(proxy, arguments); $(document.queue).each(function(index) { if (this[0] === event) { proxy.trigger.apply(proxy, this); document.queue.splice(index, 1); return false; } });});
  • 131. ultra high-performance achieved!
  • 132. ultra high-performance achieved!1. use modular programming.2. use object-oriented widgets as code building blocks.3. delegate anything and everything you can.4. use pubsub for everything else.
  • 133. onesec
  • 134. about me
  • 135. about me• I like Land Cruisers.• lived in Costa Rica for 10 years (there is no excuse for how I speak).• UI dev lead / mobile developer at Backcountry.
  • 136. questionspreguntas?