jquery anti-patterns for performance & compression
TRANSCRIPT
jQuery Anti-Patterns for Performance & CompressionPaul Irish
jQuery Conf ’09
Me.
Interaction Designer at Molecular, Inc.
@paul_irish
http://paulirish.com Front-end development blog
http://aurgasm.us Eclectic music blog
Performance
Performance
0
50
100
150
200
YUI Dojo 1.3.1 Dojo 1.2.3 Qooxdoo MooTools Prototype.js jQuery PureDOM
Taskspeed Test Lines of Code
... but, jQuery is so sexy!
Oft cited best practices
Cache length during loops
Cache your selections
Leverage documentFragment
Append new content outside the loop
Oft cited best practices
Cache length during loops
Cache your selections
Leverage documentFragment
Append new content outside the loop
BORING
Keep things DRY
If you’re repeating yourself, you’re doing it wrong
Moar DRY plz?
from http://mt-ventures.com/_js/global.js
if ($ventfade.data('currently') != 'showing') { $ventfade.stop();}if ($venthover.data('currently') != 'showing') { $venthover.stop();}if ($spans.data('currently') != 'showing') { $spans.stop();}
All clean! Thx
var elems = [$ventfade,$venthover,$spans];
$.each(elems,function(k,v){ if (v.data('currently') != 'showing'){ v.stop(); }})
Architecture Anti-Patterns
Anonymous functions bound everywhere suck
$(document).ready(function(){ ... $('#magic').click(function{ $('#yayeffects').slideUp(function(){ ... }); }); $('#happiness').load(url+' #unicorns',function(){ ... }) });
Architecture - Object Literalvar PI = { onReady : function(){ ... $('#magic').click(PI.candyMtn); $('#happiness').load(url+' #unicorns',PI.unicornCb); } candyMtn : function(){ $('#yayeffects').slideUp(PI.slideCb); } slideCb : function(){ ... }, unicornCb : function(){ ... }}
$(document).ready(PI.onReady);
Architecture - Object Literal
Advantages:
Profilers give you actual names to work with
You can execute these from firebug console
You can write unit tests against them
Anti-Pattern: The requery
// create and append your element$(document.body).append("<div class='baaron'/>");// requery to bind stuff$("div.baaron").click(function(){});
// better:// swap to appendTo to hold your elem$("<div class='baaron'/>") .appendTo(document.body) .click(function(){});
$(‘#whats .the’,context)
This is not the .context property
Ignore that for the moment, I know no one that’s found a use
// find all stylesheets in the bodyvar bodySheets = $('style',document.body);bodySheets.context // ==> BODY element
$(‘#whats .the’,context)
Never pass it a string. Ever.
No performance gain vs $(root).find(selector)
var arms = $('div.robotarm', '#container');// insteadvar arms = $('#container').find('div.robotarm');
$(‘#whats .the’,context)
You typically pass it this, but it’s purely a convenience to avoid find()
$('.reply_form', $(this).closest('.comment')).hide();
$(this).closest('.comment').find('.reply_form').hide();
$('form.comments',this).submit(captureSubmit);// exact same as$(this).find('form.comments').submit(captureSubmit);
Which is more readable?
The Crowd Say Bo Selector
Come on, my selector
Selector engines have come a long, long way.
Come on, my selector
Selector engines have come a long, long way.
Come on, my selector
Engines work in different ways
Top-down, bottom-up, function creation, other crazy shit
// from NWMatcher:
// selecting '.outmost #outer span'
T=e.nodeName;if(T=="SPAN"||T=="span"){while((e=e.parentNode)&&e.nodeType==1){if((n=e.getAttributeNode("id"))&&n.value=="outer"){if((e=e.parentNode)&&e.nodeType==1){C=e.className;if(C&&(" "+C+" ").indexOf(" outmost ")>-1){r[X++]=N;continue main;}}}}}
Selector engines, parse directionLeft to right (Top-down) Right to left (Bottom-up)
Mootools Sizzle
Sly YUI 3
Peppy NWMatcher
Dojo Acme
Ext JS
Prototype.js
details: http://alexsexton.com/selectors/
Selector engines, parse directionLeft to right (Top-down) Right to left (Bottom-up)
Mootools Sizzle
Sly YUI 3
Peppy NWMatcher
Dojo Acme
Ext JS
Prototype.js
details: http://alexsexton.com/selectors/
div.data table.attendees .gonzalez
Selector engines, parse directionLeft to right (Top-down) Right to left (Bottom-up)
Mootools Sizzle
Sly YUI 3
Peppy NWMatcher
Dojo Acme
Ext JS
Prototype.js
details: http://alexsexton.com/selectors/
Selector engines, parse directionLeft to right (Top-down) Right to left (Bottom-up)
Mootools Sizzle
Sly YUI 3
Peppy NWMatcher
Dojo Acme
Ext JS
Prototype.js
querySelectorAll (qSA)
details: http://alexsexton.com/selectors/
Selector Optimization
Specific on the right, light on the left
// let's find scottdiv.data table.attendees .gonzalez // better: drop the middlediv.data .gonzalez// best, light on the left.data td.gonzalez
tag.class if possible on your right-most selector. just tag or just .class on left.
Selector Optimization
Of course, descending from an #id is best
// basic #id-based selectorvar arms = $('#container div.robotarm');
// hyper-optimized #id case first, then find:var arms = $('#container').find('div.robotarm');
Selector Optimization
Don’t be needlessly specific
// let's find scottdiv.data table.attendees .gonzalez
// better: drop the middlediv.data .gonzalez
A flatter DOM helps, so move to HTML5
Also a wider range of tags speeds up filters
Selector Optimization
Avoid the universal selector
Avoid the implied universal selector
$('.buttons > *') // terribly costly$('.buttons').children() // much better
$('.gender :radio') // implied universal$('.gender *:radio') // exact same, explicit now$('.gender input:radio') // much better
Selector Optimization
Google PageSpeed’s efficient selectors analysis
MDC: Writing Efficient CSS
https://developer.mozilla.org/en/Writing_Efficient_CSS
Benchmark.js
http://code.paulirish.com/sandbox/benchmark.js
Event delegation
function delegate(type, delegate, handler) { return this.bind(type, function(event) { var target = $(event.target); if (target.is(delegate)) { return handler.apply(target, arguments); } });}
delegate('click','td.jehl',createRockstar);
// and with live():$('td.jehl').live('click',createRockstar);
Event Delegation
live() isn’t just for dynamic content
Speeds up page load
Only one event handler is bound vs many
Good for >3 elements all getting the same handler
// speed up live on page loadvar jqElem = $(document); jqElem.selector = 'li.ui';jqElem.live('dblclick', dblhandler);
The DOM is slow
Pull elements off the DOM while you toy with them
var table = $('#some-table'); var parent = table.parent(); table.remove(); table.addLotsAndLotsOfRows(); parent.append(table);
Minimize DOM touches
Use classes, but if a style change user-selected:
jQuery('a.swedberg').css('color', '#BADA55');
jQuery('<style type="text/css"> a.swedberg { color: BADA55; } </style>') .appendTo('head');
0
750
1500
2250
3000
1 5 10 20 50
css()style tag
Timings for X elements
(1000 iterations)
Minimize DOM touches
Don’t treat jQuery as a Black Box
Use the source as your documentation
Add this to your bookmark bar, NOW!
http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js
http://bit.ly/jqsource
Determine which are convenience methods:getScript: function( url, callback ) { return jQuery.get(url, null, callback, "script");},getJSON: function( url, data, callback ) { return jQuery.get(url, data, callback, "json");},
Don’t treat jQuery as a Black Box
Learn the lesser-known methods
map(), slice(), stop(), (de)queue(), prevAll(), pushStack(), inArray() , etc
// index() in jQuery <= 1.3.2$('#rdworth').parent().children().index( $('#rdworth')[0] )
// using prevAll() is 10% faster (also sexier)$('#rdworth').prevAll().length
// in jQuery 1.3.3$('#rdworth').index()
Don’t act on absent elements
jQuery is very kind and doesn’t throw errors at you
Don’t assume it’s just fine to do
$('#doesntexist').slideUp() // this will execute genFx(), speed() and animate() // before it hits an each()
jQuery UI widgets have a lot of overhead you’ll hit
Don’t act on absent elements
jQuery.fn.doOnce = function(func){ this.length && func.apply(this); return this; }$('li.cartitems').doOnce(function(){ // make it ajax! \o/});
Don’t act on absent elements
$.fn.plugin = function(opts){ if(!this.length) return this; var opts = $.extend(...... ... return this.each(...
Write maintainable code
As a developer,
you should work first and foremost
for the user of your products.
The second most important person to work for is
the developer that takes over from you.
- Christian Heilmann
Compression
Compression
YUI Compressor
Sits on Rhino. Kind of like an oxpecker.
Comments, whitespace, variable replacement
//it already does these micro-optimizations:object['prop'] ==> object.prop{'key':123} ==> {key:123}'jon\'s apostophes' ==> "jon's apostrophes"'bigass ' + 'string' ==> 'bigass string'
Variable definition
// old 'n bustedvar test1 = 1;var test2 = function() { // function code};var test3 = test2(test1);
// new hotnessvar test1 = 1, test2 = function() { // function code }, test3 = test2(test1);
Munge the primitives
Define shortcuts at the top of your scope
Good for both compression and scope chain traversal
var TRUE = true, FALSE = false, NULL = null, window = self, undefined = undefined;
Munge the primitives
Define shortcuts at the top of your scope
Good for both compression and scope chain traversal
var TRUE = true, FALSE = false, NULL = null, window = self, undefined = undefined;
var TRUE = true, FALSE = false, NULL = null, window = self, undefined;
var str=‘Let\’s put this into action’
// if we've got javascript, we'll switch the classvar elem = document.getElementsByTagName('html');elem.className = elem.className.replace('no-js','js');
// quicker reference, safer replacevar elem = document.documentElement;elem.className = elem.className.replace(/\bno-js\b/,'js');
// one line ftw!document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/, 'js');
// shorter with a self-executing anonymous function(function(B){B.className=B.className.replace(/\bno-js\b/,
var str=‘Let\’s put this into action’
// if we've got javascript, we'll switch the classvar elem = document.getElementsByTagName('html');elem.className = elem.className.replace('no-js','js');
// quicker reference, safer replacevar elem = document.documentElement;elem.className = elem.className.replace(/\bno-js\b/,'js');
// one line ftw!document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/, 'js');
// shorter with a self-executing anonymous function(function(B){B.className=B.className.replace(/\bno-js\b/, 'js')})(document.documentElement);
// pass className, object string notation(function(H,C){H[C]=H[C].replace(/\bno-js\b/,'js')})(document.documentElement,'className')
Conditionals
// old 'n bustedif ( type === 'foo' || type === 'bar' ) {}
// regex testif ( /^(?:foo|bar)$/.test(type) ) {}
// obj literal lookup (smaller if <5 items)if ( ({foo:1,bar:1})[type] ) {}
Logic and Ternary operands
// basic function detectiondocument.querySelectorAll && document.querySelectorAll('a:nth-child(2)') // assignment is legal, but it evaluates to the right expression callback && (isCallbackCalled = true) && callback(returnVal);
// call or cache the callback function(isCallbackCalled || returnVal) ? fn(returnVal) : (callback = fn);
// inline function callsisToday('Saturday') && Math.round(Math.random()) && $('#winnar').show()
// if JSON2.js or Native JSON is present, otherwise eval.data = window.JSON && JSON.parse(data) || eval('('+data +')');
Comments
/*! * Will not be removed by YUI Compressor */
// for quick toggling on and off:/* */ aaaahYeah();/* */
/* * / ohHellNo();/* */
Compression Tools
CompressorRater
http://compressorrater.thruhere.net/
YUI Compressor front-end
http://refresh-sf.com/yui/
Case Study: Modernizr
Thanks, ya’ll.
Slides at http://paulirish.com/perf
@paul_irish
thx:Alex Sexton, Ben Alman, Adam Sontag, James Padolsey, #jquery on Freenode
todo
shadow effect to code samples
more context research and this: http://groups.google.com/group/jquery-dev/msg/b4b7935a4013dfe7 and http://ispeakwebstuff.co.uk/web-design-development-tutorials/clever-jquery-selectors/
// pngfix for IE6// e.g. FL.pngfix('img.bigProdShot,a.thumb');pngfix : function(sel){ // conditional comments for inclusion of that js. if (typeof DD_belatedPNG == 'undefined'){ return; } else { // delay pngfix until window onload $(window).load(function(){ $(sel).each(function(){ DD_belatedPNG.fixPng(arguments[1]); }); }); }} // end of FL.pngfix()