High-Performance
Kick-Ass
Web Apps
(with focus on JavaScript)
Stoyan Stefanov, @stoyanstefanov
April 25, 2009
JSConf, Washington, D.C.
About me
• Yahoo! Search
• Yahoo! Exceptional
Performance
• YSlow 2.0 architect
• [Link]
• Books, articles
• [Link]
Importance of performance
• 500 ms slower = 20% drop in
traffic (Google)
Importance of performance
• 500 ms slower = 20% drop in
traffic (Google)
• 400 ms slower = 5-9% drop in
full-page traffic (Yahoo!)
Importance of performance
• 500 ms slower = 20% drop in
traffic (Google)
• 400 ms slower = 5-9% drop in
full-page traffic (Yahoo!)
• 100 ms slower = 1% drop in
sales (Amazon)
Importance of performance
• Self-regulating system
• Slow down = lose users
• It’s about user experience
“The premature optimization…
• … is the root of all evil”
Knuth
• “Make it right before you
make it fast”
Crockford
Pick your battles
• measure
• profile
• monitor
On trade-offs
“…everything has its drawbacks,
as the man said when his
mother-in-law died, and they
came upon him for the funeral
expenses.”
Jerome K. Jerome
Three Man in a Boat
The Life of Page 2.0
HTML page
request onload settles request
sent
marriage? R.I.P.
conception birth graduation
User perceived
“onload” happens
somewhere here
The waterfall
The Waterfall
1. Less stuff
2. Smaller stuff
3. Out of the way
4. Start early
The Waterfall
1. Less stuff
2. Smaller stuff
3. Out of the way
4. Start early
Less HTTP requests
• Combine components
Less HTTP requests
• Before:
<script src="[Link]"></script>
<script src="[Link]"></script>
<script src="[Link]"></script>
<script src="[Link]"></script>
Less HTTP requests
• After:
<script
src="[Link]"
type="text/javascript">
</script>
Less HTTP requests
• You just saved 3 HTTP requests
Less HTTP requests
• repeat for CSS:
<link
href="[Link]"
rel="stylesheet"
type="text/css”
/>
Less HTTP requests
• Inline images:
CSS sprites
with data: URI scheme
Less HTTP requests
• data: URI scheme
$ php ‐r "echo base64_encode(file_get_contents('[Link]'));”
iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4
DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC
Less HTTP requests
• data: URI scheme
background‐image: url("data:image/png;base64,iVBORw0KG...");
Less HTTP requests
• data: URI scheme
<img src="data:image/png;base64,iVBOR..." />
Less HTTP requests
• data: URI scheme
• works in IE!...
Less HTTP requests
• data: URI scheme
• works in IE8!
Less HTTP requests
• data: URI scheme
• MHTML for IE < 8
[Link]
[Link]
Less stuff? Cache
• Cache is less universal than
we think
• You can help
[Link]
“never expire” policy
• Static components with far-
future Expires header
• JS, CSS, img
ExpiresActive On
ExpiresByType image/png "access plus 10 years"
Inline vs. external
• a.k.a. less http vs. more
cache
• how about both?
Inline vs. external
• First visit:
1. Inline
2. Lazy-load the external file
3. Write a cookie
Inline vs. external
• Later visits:
1. Read cookie
2. Refer to the external file
The Waterfall
1. Less stuff ✔
2. Smaller stuff
3. Out of the way
4. Start early
The Waterfall
1. Less stuff
2. Smaller stuff
3. Out of the way
4. Start early
Gzip
Source: Bill Scott, Netflix
Minify
• Before
/**
* The dom module provides helper methods for
* manipulating Dom elements.
* @module dom
*
*/
(function() {
var Y = [Link], // internal shorthand
getStyle, // for load time browser branching
setStyle, // ditto
propertyCache = {}, // for faster hyphen converts
reClassNameCache = {}, // cache regexes for className
document = [Link]; // cache for faster lookups
[Link]._id_counter = [Link]._id_counter || 0;
Minify
• After
(function(){var
B=[Link],K,I,J={},F={},M=[Link];[Link]._id_counter=[Link]
v._id_counter||0;
Minify
• YUI Compressor
• Minifies JS and CSS
• Tolerates * and _ hacks
• More than minification
Minify
• Minify inline code too
Gzip or minification?
• 62,885 bytes - original jQuery (back in
Aug 2007)
• 31,822 - minified with the YUI
Compressor
• 19,758 - original gzipped
• 10,818 - minified and gzipped FTW
[Link]
204
• The world’s smallest component?
• 204 No Content
<?php
header("HTTP/1.0 204 No Content");
// .... do your job, e.g. logging
?>
[Link]
The Waterfall
1. Less stuff ✔
2. Smaller stuff ✔
3. Out of the way
4. Start early
The Waterfall
1. Less stuff
2. Smaller stuff
3. Out of the way
4. Start early
Free-falling waterfalls
• Less DNS lookups – fetch
components from not more
than 2-4 domains
• Less redirects
• Blocking JavaScript
Not free-falling
JavaScript rocks!
• But also blocks
html
js
png
png
Non-blocking JavaScript
• Include via DOM
html
js
png
png
var js = [Link]('script');
[Link] = '[Link]';
var h = [Link]('head')[0];
[Link](js);
Non-blocking JavaScript
• And what about my inline
scripts?
• Setup a collection (registry)
of inline scripts
Step 1
• Inline in the <head>:
var myapp = {
stuff: []
};
Step 2
• Add to the registry
Instead of:
<script>alert('boo!');</script>
Do:
<script>
[Link](function(){
alert('boo!');
});
</script>
Step 3
• Execute all
var l = [Link];
for(var i = 0, i < l; i++) {
[Link][i]();
}
Blocking CSS?
But they do block:
• In FF2
• When followed by a script
The Waterfall
1. Less stuff ✔
2. Smaller stuff ✔
3. Out of the way ✔
4. Start early
The Waterfall
1. Less stuff
2. Smaller stuff
3. Out of the way
4. Start early
flush() early
html
png
js
css
html
js
png
✔
css
flush()
<html>
<head>
<script src="[Link]"
type="text/javascript"></script>
<link href="[Link]"
type="text/css" rel="stylesheet" />
</head>
<?php flush() ?>
<body>
....
The Waterfall
1. Less stuff ✔
2. Smaller stuff ✔
3. Out of the way ✔
4. Start early ✔
Life after onload
Life after onload
1. Lazy-load
2. Preload
3. XHR
4. JavaScript optimizations
Lazy-load
• bells & whistles
• badges & widgets
Preload
• to help next page’s
waterfall
• img, CSS, JS, DNS lookups
XHR (Ajax)
• small – gzip, JSON
• less – Expires
• GET over POST
GET vs. POST for XHR
var url = '[Link]';
var request = new XMLHttpRequest();
[Link]("POST", url, false);
// …
[Link]('test=1');
GET vs. POST for XHR
JavaScript optimizations
• local vars
• DOM
• garbage collection
• init-time branching
• memoization
• threads
Local variables
• globals are all sorts of bad
• use var
• localize globals
Local variables
var a = 1;
(function(){
var a = 2;
function b(){
var a = 3;
alert(a);
}
b();
})(); // 3
Local variables
var a = 1;
(function(){
var a = 2;
function b(){
// var a = 3;
alert(a);
}
b();
})(); // 2
Local variables
var a = 1;
(function(){
// var a = 2;
function b(){
// var a = 3;
alert(a);
}
b();
})(); // 1
Local variables
• less crawling up the scope chain
• localize
• function pointers too
• help YUI compressor (it won’t rename
globals)
Wait!
Isn’t that a
micro-optimization?
Localize DOM access
function foo(){
for (var i = 0; i < 100000; i++) {
[Link]('head');
}
}
foo();
Localize DOM access
function foo(){
var get = [Link];
for (var i = 0; i < 100000; i++) {
get('head');
}
}
4
foo();
times
faster
Touching the DOM
function foo() {
for (var count = 0; count < 1000; count++) {
[Link] += 1;
}
}
Touching the DOM
function foo() {
var inner = '';
for (var count = 0; count < 1000; count++) {
inner += 1;
}
[Link] += inner;
1000
}
times
faster
Cleaning up after yourself
• Properties you no longer need
var myApp = {
prop: huge
};
// ...
delete [Link];
Cleaning up after yourself
• DOM elements you no longer
need
var el = $('mydiv');
[Link](el);
Cleaning up after yourself
• DOM elements you no longer
need
var el = $('mydiv');
delete [Link](el);
Init-time branching
• Instead of…
function myEvent(el, type, fn) {
if ([Link]) {
[Link](type, fn, false);
} else if ([Link]) {
[Link]("on" + type, fn);
} else {…
}
Init-time branching
• Do…
if ([Link]) {
var myEvent = function (el, type, fn) {
[Link](type, fn, false);
}
} else if ([Link]) {
var myEvent = function (el, type, fn) {
[Link]("on" + type, fn);
}
}
Lazy definition
function myEvent(el, type, fn) {
if ([Link]) {
myEvent = function(el, type, fn) {
[Link](type, fn, false);
};
} else if ([Link]) {
//...
}
return myEvent(el, type, fn);
}
Memoization
• for expensive, repeating tasks
function myFunc(param){
if (![Link]) {
[Link] = {};
}
if (![Link][param]) {
var result = {}; // …
[Link][param] = result;
}
return [Link][param];
}
Threads
• Web Workers for modern browsers
var myWorker = new Worker('my_worker.js');
[Link] = function(event) {
alert("Called back by the worker!");
};
[Link]
Threads
• … or setTimeout() for the rest
1. Do a chunk of work
2. setTimeout(chunk, 1) and return/yield
Life after onload
1. Lazy-load ✔
2. Preload ✔
3. XHR ✔
4. JavaScript optimizations ✔
YUI3
[Link]
YUI3
• Lighter
less KB, modules, sub-modules
• Faster
opportunity to refactor
• A la carte modules
YUI3 a la carte
• Combo handler
[Link]
• Self-populating
YUI().use(“anim”, function(Y) {
var a = new [Link]({...});
[Link]();
});
Thank you!
Stoyan Stefanov
@stoyanstefanov
[Link]
Credits/Further reading
• [Link]
• [Link]
• [Link]
• [Link]
• [Link]
• [Link]
• [Link]
• [Link]
• [Link]
• [Link]