perf&compression
perf&compression
perf&compression
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
jQuery Anti-Patterns for
Performance &
Compression
Paul Irish
NC JavaScript Camp ’10
Me.
•
•
Interaction Designer at Molecular, Inc.
jQuery Team Member - Dev. Relations
•
•
@paul_irish
http://paulirish.com Front-end development blog
http://aurgasm.us Eclectic music blog
•
Performance
wassup shawty? how u doin’
Taskspeed Test Lines of Code
200
160
120
80
40
0
YUI
Dojo 1.3.1
Dojo 1.2.3
Qooxdoo
MooTools
Prototype.js
jQuery
PureDOM
Taskspeed Test Lines of Code
YUI
Dojo Qooxdoo MooTools Prototype.js
jQuery PureDOM 040
80 120 160 200
1.3.1 1.2.3
Oft cited best practices
Cache length during loops
•
// appending inside. bad.
$.each(reallyLongArray, function(count, item) {
•
});
Cache your selections
var newLI = '' + item + '';
$('#ballers').append(newLI);
•
Leverage documentFragment
Append new content outside the loop
•
// documentFragment off-DOM
var frag = document.createDocumentFragment();
$.each(reallyLongArray, function(count, item) {
var newLI = '' + item + '';
frag.appendChild(newLI[0]);
});
$('#ballers')[0].appendChild(frag);
Keep things DRY
If you’re repeating
yourself, you’re doing it
wrong
Moar DRY plz?
if ($ventfade.data('currently') != 'showing') {
$ventfade.stop();
}
if ($venthover.data('currently') != 'showing') {
$venthover.stop();
}
if ($spans.data('currently') != 'showing') {
$spans.stop();
}
from http://mt-ventures.com/_js/global.js
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(e){
});
$('#yayeffects').slideUp(function(){
...
});
$('#happiness').load(url+' #unicorns',function(){
...
})
});
Architecture - Object Literal
var PI = {
onReady : function(){
...
$('#magic').click(PI.candyMtn);
$('#happiness').load(url+' #unicorns',PI.unicornCb);
},
candyMtn : function(e){
$('#yayeffects').slideUp(PI.slideCb);
},
slideCb : function(){
...
},
unicornCb : function(){
...
}
}
$(document).ready(PI.onReady);
Architecture - Object Literal
•
Advantages:
•
•
•
Easier to navigate and discuss
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("");
// requery to bind stuff
$("div.baaron").click(function(){});
// better:
// swap to appendTo to hold your elem
$("")
.appendTo(document.body)
.click(function(){});
$(‘#whats .the’,context)
• This is not the .context property
// find all stylesheets in the body
var bodySheets = $('style',document.body);
bodySheets.context // ==> BODY element
• Ignore that for the moment, I know no one that’s
found a use
$(‘#whats .the’,context)
• Never pass it a selector string. Ever.
• No performance gain vs $(root).find(selector)
var arms = $('div.robotarm', '#container');
// instead do:
var arms = $('#container').find('div.robotarm');
$(‘#whats .the’,context)
• You typically pass it this, but it’s purely a
convenience to avoid find()
$('form.comments',this).submit(captureSubmit);
// exact same as
$(this).find('form.comments').submit(captureSubmit);
Which is more
•
readable?
$('.reply_form', $(this).closest('.comment')).hide();
$(this).closest('.comment').find('.reply_form').hide();
The Crowd Say Bo Selector
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 direction
div.data table.attendees .gonzalez
•
Left to right (Top-down)
Mootools
Right to left (Bottom-up)
Sizzle
Sly YUI 3
Peppy
Dojo Acme
NWMatcher
querySelectorAll (qSA)
Ext JS
Prototype.js
details: http://alexsexton.com/selectors/
Selector Optimization
•
Specific on the right, light on the left
// let's find scott
div.data .gonzalez
// specific on right, 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 selector
var 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 scott
.data table.attendees td.gonzalez
// better: drop the middle
.data td.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 $(document).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
// using live(), skipping selection on load
var jqElem = $(document);
jqElem.selector = 'li.ui';
jqElem.live('dblclick', dblhandler);
Event Delegation
new
in
1.4.2!
•
•
•
delegate() bakes in huge performance gains
explicit context reduces overhead by ~80%
Use it instead of live() if possible
// awkward but equivalent
$('a.trigger',$('#container')[0]).live('click',handlerFn)
// so damn fine
$('#container').delegate('click','a.trigger',handlerFn)
The DOM is slow
•
Pull elements off the DOM while you toy with them
var table = $('#some-table');
var parent = table.parent();
table.detach();
table.addLotsAndLotsOfRows();
parent.append(table);
new in
1.4
Minimize DOM touches
•
Use classes, but if a style change user-selected:
jQuery('a.swedberg').css('color', '#BADA55');
jQuery(' a.swedberg { color: BADA55; } ')
.appendTo('head');
4000
3000
Timings for X elements
(1000 iterations)
2000
1000
css() style tag
css()
style tag
0
1
5
10
20
50
1510
50 01000
2000 3000 4000
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
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(...
Setter Methods
view-source:setters.js
new
New Element Creation
in
1.4!
jQuery("", {
id: "foo",
rel : "something"
css: {
height: "50px",
width: "50px",
color: "blue",
backgroundColor: "#ccc"
},
click: function() {
$(this).css("backgroundColor", "red");
}
}).appendTo("body");
new
eq(), first(), last()
in
1.4!
var lastelem = $elems.eq(-1); // get() too!
$('#nav li:first') === $('#nav li').first()
$('#nav li:last') === $('#nav li').last()
Data()
// regular:
$(elem).data(key,value);
// omg like 10x faster:
$.data(elem,key,value);
Compression
Compression
•
YUI Compressor
•
Sits on Rhino.
•
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 busted
var test1 = 1;
var test2 = function() {
// function code
};
var test3 = test2(test1);
// new hotness
var 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
(function(){
var window = this, document = document, undefined;
/* code */
})();
(function(window, document, undefined){
/* code */
})(this,this.document);
var str=‘Let\’s put this into action’
// html.no-js ==> html.js
var elem = document.getElementsByTagName('html')[0];
elem.className = elem.className.replace('no-js','js');
// quicker reference, safer replace
var elem = document.documentElement;
elem.className = elem.className.replace(/\bno-js\b/,'js');
// change the html class to 'js'
// one line ftw!
document.documentElement.className // in the head, no FOUC=
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/,
Conditionals
// old 'n busted
if ( type === 'foo' || type === 'bar' ) {}
// regex test
if ( /^(foo|bar)$/.test(type) ) {}
// obj literal lookup (smaller if
Logic and Ternary operands
// basic function detection
document.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 calls
isToday('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 +')');
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
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/
•
Thanks, ya’ll.
•
Slides at http://paulirish.com/perf
•
@paul_irish
thx:
Alex Sexton, Ben Alman, Adam Sontag,
•
James Padolsey, temp01, #jquery on Freenode
todo
•
shadow effect to code samples
more context research and this:
•
http://groups.google.com/group/jquerydev/msg/b4b7935a4013dfe7
and
http://ispeakwebstuff.co.uk/web-design-developmenttutorials/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()