Leverage patterns of large-scale JS – such as modules, publish-subscribe and delegation – to achieve extreme performance without sacrificing maintainability.
19. var transformer = function($) {
var _transform = function(sel) {
$(sel).toggleClass('robot');
}
}(jQuery);
20. module pattern
module consists of 3 parts:
1. function (what it does)
2. dependencies (what it needs)
3. interface (what it returns)
21. var transformer = function($) {
var _transform = function(sel) {
$(sel).toggleClass('robot');
}
// sets what `transformer` is equal to
return = {
transform: _transform
}
}(jQuery);
22. var transformer = function($) {
var _transform = function(sel) {
$(sel).toggleClass('robot');
}
// sets what `transformer` is equal to
return = {
transform: _transform
}
}(jQuery);
// usage
transformer.transform('.car');
23. var transformer = function($) {
var _transform = function(sel) {
$(sel).toggleClass('robot');
}
// sets what `transformer` is equal to
return = {
transform: _transform
}
}(jQuery);
// usage
transformer.transform('.car');
// result
<div class="car robot" />
24. benefits of modular programming
• self contained – includes everything it needs
to accomplish it's function.
• namespaced – doesn't dirty the global
scope.
25. // you can’t do this… yet
import "transformer.js" as transformer;
32. 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.
35. // dependency array is the first parameter of define
// dependencies mapped to parameters in the callback
define([
'jquery'
], function($) {
var _transform = function(sel) {
$(sel).toggleClass('robot');
}
return = {
transform: _transform
}
});
36. // dependency array is the first parameter of define
// dependencies mapped to parameters in the callback
define([
'jquery',
'underscore'
], function($, _) {
var _transform = function(sel) {
$(sel).toggleClass('robot');
}
return = {
transform: _transform
}
});
51. // run it manually
// or as part of automated build process
java -classpath r.js/lib/rhino/js.jar
org.mozilla.javascript.tools.shell.Main
r.js/dist/r.js -o build.js
52. // example output
Tracing dependencies for: common
common.js
----------------
jquery.js
ui/jquery.ui.core
ui/jquery.ui.widget
common.js
56. 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).
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');
}
});
62. 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"
65. // original
if (has('dev')) {
console.log('test');
}
// after r.js pre-processing
if (false) {
console.log('test');
}
66. // original
if (has('dev')) {
console.log('test');
}
// after r.js pre-processing
if (false) {
console.log('test');
}
// after uglify post-processing
// nothing – uglify strips dead code branches
69. even better performance with almond
intended for single page apps or mobile where
request latency is much worse than desktop.
• require.js = 16.5k minified (6k gzipped)
• almond.js = 2.3k minified (~1k gzipped)
71. 1st step to ultra high performance
use modular programming.
• combine with require.js for asynchronous /
parallel loading.
• automatic concatenation, optimization.
• for ultra performance use almond.js.
72. anyone not use jquery?
“Study shows half of all websites use jQuery”
– August, 2012
73. // example of a jquery plugin used with a module
define([
'jquery',
'jquery.craftyslide'
], function($) {
$('#slideshow').craftyslide();
});
74. // closer look at `craftyslide.js`
$.fn.craftyslide = function (options) {
function paginate() {
…
}
function captions() {
…
}
function manual() {
…
}
paginate(); captions(); manual();
}
75. problem with jquery plugins
they’re a black box.
• not easily extendable.
• not easily testable.
76. problem with jquery plugins
they’re a black box.
• not easily extendable.
• not easily testable.
jquery ui set out to solve this with…
78. oh noes! not jquery ui
bloated 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).
79. ui widgets
the two things plugins suck at, widgets do
really well:
• they're fully extendable.
80. simple javascript inheritence
25 lines of javascript sexiness:
• constructors.
• object-oriented inheritence.
• access to overridden (super) methods.
81. simple javascript inheritence
25 lines of javascript sexiness:
• constructors.
• object-oriented inheritence.
• access to overridden (super) methods.
also the foundation of ui widget extensibility.
83. // example widget
$.widget('ui.transformer', {
options: {
…
},
_create: function() {
…
}
);
// extending it
$.widget('ui.autobot', $.ui.transformer, {
// extend anything or everything
});
84. not-so simple javascript inheritence
everything from simple javascript inheritence,
plus:
• namespaces.
• public and private methods.
• getters/setters.
• disable/enable.
85. ui widgets
the two things plugins suck at, widgets do
really well:
• they're fully extendable.
• they're tuned for testing.
86. // if `craftyslide` were a widget
$.widget('ui.craftyslide', {
_create: function() {
…
this._paginate();
this._captions();
this._manual();
},
_paginate: function(){ … },
_captions: function(){ … },
_manual: function(){ … }
);
87. // adding triggers as hooks for testing
$.widget('ui.craftyslide', {
…
_paginate: function(){
this._trigger('beforePaginate');
…
this._trigger('afterPaginate');
},
…
);
88. // in your unit test
function beforePaginate() {
// test conditions
}
function afterPaginate() {
// test conditions
}
$('#slideshow').craftyslide({
beforePaginate: beforePaginate,
afterPaginate: afterPaginate
});
89. // plugin using `.on()`
function manual() {
…
$pagination.on('click', function (e) {
…
});
}
90. // plugin using `.on()`
function manual() {
…
$pagination.on('click', function (e) {
…
});
}
// widget using `._on()`
manual: function() {
this._on($pagination, { click: '_click' }
}
91. // `._on()` remembers all event bindings
_on: function( element, handlers ) {
…
this.bindings = this.bindings.add( element );
},
92. // `._on()` remembers all event bindings
_on: function( element, handlers ) {
…
this.bindings = this.bindings.add( element );
},
// `.remove()` triggers a `remove` event
this._on({ remove: "destroy" });
93. // `._on()` remembers all event bindings
_on: function( element, handlers ) {
…
this.bindings = this.bindings.add( element );
},
// `.remove()` triggers a `remove` event
this._on({ remove: "destroy" });
// `.destroy()` cleans up all bindings
// leaving the DOM pristine
destroy: function() {
…
this.bindings.unbind( this.eventNamespace );
}
96. 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
}
});
});
97. 2nd step to ultra high performance
use object-oriented widgets as code building
blocks.
• inheritance promotes code re-use, smaller
codebase.
• built on an architecture that promotes
testability.
98. made possible via
• fast file loading.
• small file sizes.
• avoiding DOM bottlenecks.
102. <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
});
103. // event delegation is similar
$('#transformers').on('click', 'a', function() {
// do something
});
104. // 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
});
105. 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
don't need to re-attach the event listener.
• faster – you can bind the event listener to
document as soon as the javascript has
loaded, you don't need to wait for domready.
106. how does this work with widgets?
it doesnt – widget's pitfall is they are a DOM
bottleneck.
110. // new widgets
$.widget('ui.lightbox', {
_create: function() {
var sel = this.options.selector;
var handler = {};
handler['click ' + sel] = 'show’;
this._on(handler);
}
});
111. // 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'
});
112. 3rd step to ultra high performance
delegate 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!
113. delegation isn’t a cure all
delegation works great when the widget
doesn't need to know about the user up until
the user interacts with it.
but what about widgets that need to affect the
DOM on instantiation…
114. how we’ve done this previously
• document.load – the 80's of the internet.
• document.DOMContentLoaded – the new
load event!
115. domready considered an anti-pattern
“the short story is that we don't want to
wait for DOMContentReady (or worse the
load event) since it leads to bad user
experience. the UI is not responsive until
all the DOM has been loaded from the
network. so the preferred way is to use
inline scripts as soon as possible”
– Google Closure team
125. oh no you didn't
two problems with our modular approach:
• nothing is exposed to the global scope
– you can't use modules from the
DOM.
• if the JS is loaded asynchronously you
don't know that it's available when the
browser is parsing the HTML.
126. <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>
127. // bootstrap.js
// needs to be some global object
// but we can clean it up afterwards
document.queue = [];
window.publish = function() {
document.queue.push(arguments);
}
133. 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.
136. 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.