Even Faster Websites
Cuzillion (web performance tool like YSlow)
Steve Souders: High Performance Web Sites: 14 Rules for Faster Pages
Gopal Venkatesan: Writing Efficient JavaScript
Julien Lecomte: High Performance Ajax Applications
Nicholas Zakas: Maintainable JavaScript
Joseph Smarr: High-performance JavaScript: Why Everything You've Been Taught is Wrong
Make fewer HTTP requests
- CSS Sprites
- combine scripts
- combine stylesheets
Use a CDN (Content Delivery Network)
Distribute your static contents. CDN providers: Akamai, Mirror Image, SAVVIS, LimeLight. Pick one that can distribute your static contents using GEO-IP.
Distribute your static contents before distributing your dynamic contents (having multiple data centers, or bring your dynamic content servers and databases closer to the visitor). Know where your visitors are coming from before deciding on where to put your dynamic content servers and databases.
Maximize the use of browser cache by adding appropriate cache control directive
Gzip components
Gzip html, scripts, stylesheets, XML, but not binary content (images, PDFs)
Put stylesheets at the top
Use LINK tag instead of @import
Move scripts to the bottom
- scripts block the rendering of anything below them in the page
- scripts also block parallel downloads (images and stylesheets below the script) across all hostnames. In HTTP/1.1, browsers can download 2 components in parallel per hostname.
Avoid CSS expression
IE supports something call CSS expression. A lot of time, people use it to address browser incompatibilities across browsers. An expression is just a javascript expression that IE executes and the result of that expression is set as css style.
To deal with the lack of minWidth:
width: expression(document.body.clientWidth < 600 ? "600px" : "auto");
The problem: CSS expression are executed many more times than you expect. They are executed everytime you move the mouse around the page, press a key, resize the page, scroll, etc.
Workaround:
- use one-time expression (an expression that remove itself)
- use an event handler
CSS expressions are tied to the events automatically so you don't have to worry about that, but that is what is bad about it. It is tied to all the events, so figure out what event you need this behavior, and write an event handler for that.
Make CSS and JS external
For complex home page, inline CSS and JS, but as soon as the page is done loading, you can preload external CSS and JS. As the server serve the homepage, it can set a cookie. The next time the visitor hit the home page, the server see the cookie, a strong indication that those CSS and JS had been preloaded, so the server can serve the homepage without inlining those CSS and JS files.
Reduce DNS lookups
Don't need to do anything here. Operating systems cache result of DNS lookup already.
Minify the combined JS file
Minify the combined JS file. You can also minify your inline JS as well. On the server side, what ever language you use to generate the content, you can have a function to minify your inline javascript.
Avoid redirects
- adds additional delay, and are not cached unless you add appropriate cache control headers.
- worst form of blocking
Remove duplicate javascripts
Configure ETag
The problem with most common setup for ETag is that it use inode (which is different across servers), and timestamp (might not be reliable across servers). The only thing you can count on being the same across servers is the size of the file, and its content (otherwise your push script is broken). It may be nice if we can md5 the content (might add overhead), so the only thing we might be able to count on is the file size, which also has issue (if the number of characters remain the same). Turn off ETag:
FileETag none
Some AJAX requests might be cacheable in the browser
AJAX requests are usually dynamic, and personal, therefore give a false sense that it cannot be cache, but in reality, some AJAX requests are cacheable. Imagine an email application with a contact list, and the user don't update the contact list often. What you want to do is to add the timestamp of the last time the user update the contact list to the URL. When the user update his contact list, you update this timestamp in the user preference table. When you see the AJAX request with the timestamp, you can compare it with the timestamp in the preference table. Using this technique, is it possible that we can have a situation where the server thinks that the browser has the content in the cache, but it actually does not? This is definitely possible, if the user, for some reason, clear the browser cache.
Minimize the use of global variables / namespace - Use local references when possible.
Javascript sequentially search up the stack chain for variable references therefore local variables will be found first. Speed up code by creating a local variable pointing to global variable (if there need to be a global variable at all).
Variables and functions in global scope exist through the life cycle of the script, memory deallocation can only be done at the end of execution.
Cache frequently used objects and properties.
for (var i=0, len = myArray.length; i < len; i++)
is better than
for (var i=0; i < myArray.length; i++)
var s = document.getElementById('myobject').style;
s.borderWidth='1px';
s.fontsize='90%'
is better than
document.getElementById('myobject').style.borderWidth='1px';
document.getElementById('myobject').style.fontsize='90%';
Cache function pointers.
If you need to call a global function repeatedly inside a loop, assign the function reference to a local variable:
function A() {}
function B()
{
var x = A;
for (var i = 0; i < 100; i++)
{
x();
}
}
is better than:
function A() {}
function B()
{
for (var i = 0; i < 100; i++)
{
A();
}
}
Assigning the function reference to a local variable only make sense when this is happening inside an outer function.
Avoid using eval when possible.
Avoid the Function constructor.
var callback = new Function("...");
Using the Function constructor is equivalent to using eval since the string inside the constructor needs to be evaluated.
var callback = function() { ... };
is better than:
var callback = new Function("...");
Avoid the 'with' statement
Consider:
with (element.style)
{
color = '#fff';
foo = something;
}
The use of 'with' creates an extra scope, and the contents of the scope are not known ahead of time for optimization. JavaScript need to understand whether color is a global variable or it is a property of element.style. In this example, foo is not a property of element.style, a global variable is created. What is the scope of something? Is it a global variable, a local variable, a property of element.style ?
var s = element.style;
s.color = '#fff';
is better than using the 'with' statement.
Avoid using try/catch inside a loop.
The try/catch block creates a local scope and create an exception object at runtime that live in that scope.
try {
for(...) {
}
} catch(e) { ... }
is better than:
for(...) {
try {
} catch(e) { ... }
}
Minimize DOM insertion.
Browser will re-evaluate the page on each change to the DOM. Keep new element insertions to a minimum. When you do modify the DOM, it is best to lump all the insertion together.
Global variables and functions are indexed by named. Local variables are kept on stack.
Random stuff.
setTimeout(myFunc,...);
is better than:
setTimeout('myFunc()',...); // eval() is used here.
Either Array.join() or String.concat() is better than '+' when concating a lot of strings.
var s = ["hello", " world"].join("");
var s = String.concat("hello", " world");
is better than
var s = "hello" + " world";
Hide the element (if possible) whenever you need to modify a lot of properties.
Everytime you change a property, the browser has to re-render the document.
This is another recommended way:
// This technique need improvement because we need to find out what the current style is
var d = document.getElementById("...");
var style = "background: #fff; color: #000; display: block";
if (typeof(d.style.cssText) != undefined)
{
d.style.cssText = style;
} else {
d.setAttribute('style',style);
}
innerHTML is 75% faster than using DOM (maybe)
Dumping HTML into innerHTML is more than 75% faster than using DOM methods especially when adding a lot of elements. It would be interesting to try adding all the elements to a div, and then append the div to the element. (Using innerHTML is definitely more convinient.).
Having minimal re-flow
Some properties like offsetWidth and offsetHeight has internal re-flow when accessed, so cache the property (if you need to access it multiple times):
var d = document.getElementByID('some-ele');
var ow = d.offsetWidth;
// use ow from now on
Minimize the number of event handlers
If you have a group of similar elements, rather than attaching event handler to each element, attach event handler to the container.
Increase parallelism through sub-domains
Browsers allow 2 simultaneous connections per sub-domain.
Be lazy (write minimal amount of code)
- If your application is slow, it is because your code is doing stuff that is slow. The less you have to do, the better. If you can get away with not doing something now, the worst case is you have to do it later, but you may not have to do it ever at all.
- It is very hard to get a lot of code and make it run really fast.
- You pay for every line of code you write. You have to pay for the initial download time. After that, you have to pay in term of parsing, compiling and execution time. Parsing javascript is often a major bottleneck. Browsers do not have JIT, cached object code. Browser re-interpret the code everytime. Parse time is non-linear in the size of total javascript. Ideal size for large AJAX applications is less than 500K of uncompressed JS.
Use a minifier
Use a minifier. Your build system should combine JS files, remove all debug code (less parsing, compiling, and execution time), and minify the combine javascript file.
Load javascript on-demand
Break into classes and modules. Bundle modules into packages. Load the code as needed. Library like Dojo has a system that will check if the module has been loaded, if not it will make an XMLHTTP request, and eval the code.
Draw less DOM
If we have a large DOM fragment (the content of a tab) that is hidden, it is better to remove it from the DOM, and recreate it from the server when the tab is activate. Never attach hidden UI to the DOM if you can avoid it. This makes the DOM less saturated (consume less memory)
Cache previously drawn HTML when appropriate.
Don't keep hidden UI up todate behind the scene - just redraw the next time you show it.
Be responsive
- the loading message
- put CSS at the top, javascript at the bottom
- yield via setTimeout(0), use closure to maintain state across invocation
- use onmousedown instead of onclick (100ms difference)
- Just redraw (get it from the server). The amount of code that you write to avoid getting data from the server will cause the browser to slow down (parsing, compiling time)
Be pragmatic (knows when you can break the rules)
- avoid dynamic CSS class definition and CSS math
- avoid memory allocation (i.e. splitting strings)
- do DOM manipulation off-DOM and then reinsert at the end (avoid reflow)
- directly attach onclick, etc handlers instead of using event handlers where appropriate. Why give it an ID, findElementById and attach an event handler? Just use a global function (perhaps with namespace). Memory leak consideration???
- play to your browser strength
- know when you can / should not follow stuff that you've been taught.
Be vigilent
Profile early and often. Use YSlow
At my current company, we have a script that combine and minify javascript files. After editing the unminified file, we run the minification script, test, and check both the raw and minified files into source control. The push script push the minified file. This seems cumbersome but has its advantages (we actually test the minified file, the code that do minification only exist in one place). Joseph Smarr use an error handler, in dev environment, to detect that the minified version does not exist, dynamically combines javascript files and minifies the result file. If we use Joseph's approach, we need to make sure that the error handler and the build script use exactly the same code to combine and minify JS.
Object detection is better than browser detection.
Instead of
if (navigator.appName == 'Netscape')
use
if (obj.offsetHeight)





