Ben Cherry Performance Engineer at Slide, Inc.
1.The JavaScript Language
just what you need to know to understand this talk
variables and functions
var foo = 1 ; // variable statement function bar() {} // function statement ( function baz() {}); // function expression ( function (spam) {}( 1 )); // function parameter those are the only ways you should declare things
scope
var foo = 1 ;( function () { var baz = 2 ; foo = 3 ;}());alert(foo); // 3 alert(baz); // ReferenceError JavaScript has function-level scope
hoisting
var foo = 1 ;( function () { alert(foo); // undefined var foo = 2 ;}());alert(foo); // 1 names are always hoisted to the top of their scope , and always initialized to undefined
closure
var foo = ( function () { var bar = 5 ; return function () { alert(bar); };}());foo(); // 5 setTimeout(foo, 10000 ); // 5, 10s later functions have access to their original scope chain
2.Run-Time Performance common pitfalls
"namespaces"
Python
import bc.utilbc.util.some_function() // # this is quite fast JavaScript
BC.util.someFunction(); // but this is very slow here's what happens
BC.util.someFunction(); // lookup BC // resolve property util // resolve property someFunction // execute function imagine that in a loop!
store that reference
var some_function = BC.util.some_function,i; for (i = 0 ; i < 1000000 ; i += 1 ) { some_function();} this is much faster
scope chains
the slow way
var foo = 1 ; function bar() { var i; for (i = 0 ; i < 10000 ; i += 1 ) { alert(foo); }}bar(); foo
is one step down the chain
faster
var foo = 1 ; function bar() { var myFoo = foo, i; for (i = 0 ; i < 10000 ; i += 1 ) { alert(myFoo); }}bar(); now myFoo
is on the end of the chain
arrays
arrays
var a = []; typeof a; // "object" a.length; // 0 a[ 10 ] = 1 ;a.length; // 11 a[ " something_else " ] = 1 ;a.something_else; // 1 a.length; // 11 this means arrays are slower than you expect , but some browsers do optimize them
loops
for loops
// slow method var i; for (i = 0 ; i < someArray.length; i += 1 ) { // ... } // faster var i, l; for (i = 0 , l = someArray.length; i < l; i += 1 ) { // ... } for-in loops
var key; for (key in someObject) { // ... } these are slow, avoid them
var keys = [ " foo " , " bar " , " baz " ], i, l, key; for (i = 0 , l = keys.length; i < l; i += 1 ) { key = keys[key]; // ... someObject[key] ... } this is much faster, and is preferred
arguments
function foo() { return arguments[ 0 ];}foo( 1 ); // 1 much slower (up to 100x) than using parameters , arguments
should be avoided if possible
3.Inheritance
don't use it ,
unless you have to , and that's all I have to say about that modules
how to make BC.util
? not the same as inheritance
BC.util = ( function (BC) { var util = {},foo; // private variable // public util.someFunction = function (){}; // private function someOther(){} return util;}(BC)); this a fast, reusable pattern
4.The DOM
it's absolutely terrible , but I have three simple rules to make it better
rule 1:never edit the live tree
you can detach sub-trees
var elem = $( " #myelem " ), parent = elem.parent();elem.detach(); // ... muck with elem and sub-elements ... parent.append(elem); .detach()
is new in jQuery 1.4
or you can clone and replace
var old = $( " #myelem " ), clone = old.clone(); // ... muck with the clone ... old.replaceWith(clone); but be careful about event handlers , use .clone(true)
to preserve them
rule 2:build bottom-up
bottom-up construction
var child = document.createElement( " div " ), parent = document.createElement( " div " ), grand = document.createElement( " div " );parent.appendChild(child);grand.appendChild(parent);document.body.appendChild(grand); rule 3:minimize event handlers
memory leaks in IE
$( " .myelems " ).bind( " click " , function () { /* ... */ }); // ... $( " .myelems " ).remove(); // aww snap, memory leak! was that really that surprising?
unbind before removal
$( " .myelems " ).unbind( " click " ).remove(); // phew! but that kind of sucks too
use event delegation
$(".myelems").live("click", function () { /* ... */});$(".myelems").remove();$("<div/>").addClass("myelems").appendTo($("body")) the new <div>
gets the handler for free
avoid handlers in loops
function makeElem(id) { return $("<div/>").attr("id", id).click(function () { alert($(this).attr("id")); });}var i;for (i = 0; i < 1000; i += 1) { someParent.append(makeElem(i));} this is slow!
faster
function handler() { alert(this.attr("id"));}function makeElem(id) { return $("<div/>").attr("id", i).click(handler);}var i;for (i = 0; i < 1000; i += 1) { someParent.append(makeElem(i));} 5.long-running operations
sometimes you can't avoid it
problem script
var i;for (i = 0; i < 10000; i += 1) { oneMillisecondOperation(i);} browser locks for 10s!
solution: timers
var i = 0 ;setTimeout( function iterate() { var stop = i + 500 ; for (; i < stop; i += 1 ) { oneMillisecondOperation(i); } setTimeout(iterate, 20 );}, 0 ); split into half-second chunks, with 20ms in between
minimum intervals
setTimeout( function () { alert( " foo " );}, 0 ); // how long until "foo"? no browser really does 0ms ; Chrome is ~5ms, but IE is ~18ms ;others are ~10-12ms
6.page-load performance
getting your script running quickly
< html > < head >< / head> < body > <!-- all scripts at the bottom of < body >--> < script >< / script> < / body> < / html> always at the bottom, so the page is not blocked
avoid inline scripts
< script > function foo() { // ... } < / script> these cannot be cached or minified , only put dynamic values here
minification
function foo() { var bar = 1 ; return bar + 5 ;} function foo(){ var a = 1 ; return a + 5 } our build process does this for you , make sure you're using it!
7.Performance Tools
my JavaScript profiler
var profiler = performance.newProfiler(); function foo() { profiler.begin( " body " ); // ... some operations ... profiler.end( " body " );} // ... repeated calls to foo() ... profiler.report(); // alerts time spent in "body source at
other tools
- JSLint (JavaScript syntax checker)
- YSlow (Firebug extension)
- Page Speed (Firebug extension)
- Speed Tracer (Chrome extension)
- dynaTrace AJAX (Windows program/IE plugin)
8.random extras
somewhat important
parseInt
needs radix!
parseInt( " 123 " ); // 123 parseInt( " 10 " ); // 10 parseInt( " 010 " ); // 8 -> WTF? // with a radix parseInt( " 010 " , 10 ); // 10 -> crisis averted! never forget your radix
sort
has issues
var a = [ 3 , 1 , 2 ];a.sort(); // [1,2,3] a = [ 10 , 1 , 2 ];a.sort(); // [1,10,2] - f**ing javascript! a.sort(betterComparison); // [1,2,10] .sort()
sorts alphabetically , write your own comparison function
CSS expressions are evaluated right-to-left!
this starts by looking at every <a>
then it looks for those whose parent is <div>
then it checks to see if its parent is #foo
Useful Resources
because this was not nearly exhaustive
dead trees
- High Performance Web Sites, by Steve Souders (Yahoo!, at the time)
- Even Faster Web Sites, by Steve Souders (Google, now)
- JavaScript: The Good Parts, by Douglas Crockford (Yahoo!)
- High Performance JavaScript, by Nicholas Zakas (Yahoo!) (coming soon)
- Secrets of the JavaScript Ninja, by John Resig (Mozilla/jQuery) (coming soon)
blogs
- High Performance Web Sites, by Steve Souders
- NCZOnline, by Nicholas Zakas
- Douglas Crockford's JavaScript, by Doug Crockford
- Perfection Kills, by @kangax (author of Prototype.js)
- John Resig - Blog, by John Resig (creator of jQuery)
- Software is Hard, by Jan Odvarko (Firebug developer)
this slideshow
was running in the browser, with JavaScript
- source on GitHub at
- available online at
- built with ShowOff -
thanks for coming!