Overview

First understand the big picture and the major component of the stack affecting the performance. It is of utmost importance to understand what can and cannot be optimized in JavaScript without touching the browser codebase. A good starting point for this study is to first take a look at the JavaScript Performance Stack (depicted in the figure).

Pick your battles. It is generally a good approach to first optimize those parts which give you the biggest improvements.

There are many interrelated components that play an instrumental role in the real-life web application performance such as those responsible for layout, rendering, parsing HTML, marshaling, DOM, CSS formatting, JavaScript -- as you see, JavaScript is only one part of the performance equation.

The most expensive operations tend to be reflowing the layout and repainting. Although you as a JavaScript developer cannot optimize browser layout or painting algorithms you can still implicitly affect the performance of these expensive operations by trying to avoid triggering there expensive operations unnecessarily. A real-life example of IE8 tells us that layout and rendering tasks takes most time on IE8 (see webcast at -20:00 mins)

Below are some examples of common reasons for slow JavaScript performance that you as a JavaScript developer can easily fix and make your web application to perform better instantly:

DOM access

Interaction with the DOM is usually slower than normal JavaScript code. Interaction with the DOM is usually inevitable, but try to minimize it. For instance, dynamically creating HTML with strings and setting the innerHTML is usually faster than creating HTML with DOM methods.

Store local references to out-of-scope variables

When a function is executed an execution context is created and an activation object containing all local variables is pushed to the front of the context's scope chain.

Further in the chain, the slower the identifier resolution is, which means local variables are fastest.

By storing local references to frequently used out-of-scope variables reading and writing to variables is significantly faster. This is visible especially with global variables and other deep searches for identifier resolution.

The length of the array arr is recalculated every time the loop iterates.

for(var i =0; i < arr.length; i++){// do something}

Better Approach:

Better way is to to cache the length of the array:

for(var i = 0, len = arr.length; i < len; i++){// do something}

In General we should avoid sending the interpreter out to do unnecessary work once it has already done it once, eg: figuring out the scope chain or the function evaulation values of an expression used more than once.storing/caching values to variable only makes sense if those values are used repetatively or more than once, otherwise we are creating overhead again for declaring a variable, assigning values and then just using only once, so keep in mind

Tips for better loading performance

Load scripts without blocking for faster startup and to show a splash screen

When <script> tags are found in the HTML document the referenced script resources are downloaded and executed before the rendering engine can continue to download other resources which effectively blocks the rendering of the page below the tag. To avoid this blocking behaviour a script tag can be created via a mechanism known as a dynamic script tag injection.

This mechanism allows the rendering engine to immediately render and display the initial view (aka the splash screen) defined in HTML while the JavaScript resources are still being loaded and executed which leads to better user experience.

Use YUI Compressor or JSMin to compress the code

The Best: YUI Compressor provides the best overall performance when you consider it as: Total_Speed = Time_to_Download + Time_to_Evaluate. Depends on Rhino and is not applicable for real-time compression.

Simple: JSMin has implementations in nearly all the languages and is applicable for real-time compression. After gzipping the size comes very close to that of YUI Compressor.

JSMin or YUI Compressor + gzipping is better performer than Dean Edwards' Packer + gzipping. Although Packer provides the smallest (byte-size) code it will evaluate much slower as the scripts are unpacked on the client-side using one pass of a RegExp? this introduces an overhead especially significant on slow mobile devices.

Depending on your specific constraints (resources in the same/different domain, need to preserve script loading order, need to show browser loading indicator) you can do an informed decision by checking the decision tree in the following presentation (slide 26).

Document Object Model (DOM) obscurities

Slow DOM performance can be traced back into the following three main causes:

Extensive DOM manipulation

Extensive use of DOM API is a well-known cause of slowness.

Script triggers too many reflows or repaints

As a consequence of DOM manipulation, reflowing the layout and repainting are very expensive.

Slow approach to locating nodes in the DOM

Locating a desired node(s) in the DOM is potential bottleneck if the DOM is sizable and/or complex.

Minimize the size of the DOM

The size of DOM slows down all the operation related to it such as reflowing, traversal and DOM manipulation.

The most effective way to make programs faster is to make n smaller means that the DOM should be as small as possible at all times.

Minimize n, you can track the number of elements in a page by:

var n = document.getElementsByTagName('*').length;

var n = $('*').length;

Use document fragment templates for re-usability

Dynamically inserting and updating elements into the DOM is expensive. An efficient way to tackle this is to use HTML templates which can be cloned and re-used for re-usable parts of the DOM such as dialogs and other UI widgets.

In practice the approach is to modify, clone and append all nodes in JavaScript without touching the live DOM and append the completed document fragment to the DOM at once. One can do this with DOM API or alternatively construct a string representation of the HTML fragment to append based on a string template and push that into the DOM with a one innerHTML assignment. On both cases the rendering engine does not have to reflow and repaint the layout multiple times. Next we introduce some techniques to achieve this behavior.

Minimize the number of reflows and repaints

Repaint

Repainting happens when something is made visible or hidden without altering the layout of the document. For example if an outline is added to an element, its background color is changed of its visibility is changed. Repainting is an expensive operation (paint events demo).

Reflow

Reflow happens whenever the DOM is manipulated in a way it affects the layout. For example, style is changed to affect the layout, className property is changed or browser window size is changed. Once an element needs to be reflown, its children will also be reflown and any elements appearing after the element in the DOM. Finally, everything is repainted. Reflows are even more expensive operations, than repainting. In many cases reflowing is comparable to layout out the entire page again (reflow demo videos).

Operation that trigger reflows should be used sparsely.

Reflowing a table element is more expensive that reflowing equivalent element with block display.

Elements that are positioned absolutely or fixed do not affect the main document layout, so their reflowing is cheaper as they do not trigger main document reflowing. This is recommended approach for element that need to be animated.

DOM modifications trigger reflow. This means that operations such as adding new elements, changing the value of text nodes or adding element attributes and their properties cause reflow.

Use cloneNode()

If you're not working on elements that do not contain form elements or event handlers, you can clone the element to modify and swap it in place after all the changes have been done resulting in a one reflow only.

Use HTML templates and innerHTML

One way to implement a templating system is to populate template content based on a light-weight JavaScript object acting as a date model. The data model may be persisted as JSON serialization into e.g. setPreferenceForKey() store in S60 WRT, in a HTTP cookie or XHR'd to the server-side.

CSS class name vs. style property changing

Changing the class name of an element is a nice way to use JavaScript to dynamically change elements. Performance varies from browser to browser, but generally it is faster to change an element's visual appearance directly via the Javascript style attribute, rather than to change a class name on that element.

The above method appears to be more efficient when changing a specific number of items. Sometimes a single class name change is effective however. If you need to change all elements under a given container for example, it is more efficient to change the class name of a parent container which holds the affected elements and let CSS do what it does best.

Faster (if multiple child elements of a container need to be changed): // by changing the class name of the container, all of its child div elements will be updated#container.active div { border: 1px solid red; }

Depending on the specific case at hand you should use the method which gives you the best performance (without sacrificing too much of the separation of concerns benefits of externally defined CSS).

Avoid traversing large number of nodes

Always try to use inbuilt methods and collections of the DOM to narrow down the search to smallest number of nodes possible.

Try to avoid manually recursively stepping through the DOM as much as possible.

Slow:

var elements = document.getElementsByTagName('*');// searches every element, slowfor(i =0; i < elements.length; i++){if(element[i].hasAttribute('selected')){// continues even through element was found ...}}

Faster:

var elements = document.getElementById('parent').childNodes;// we know the element is a child of parentfor(i =0; i < elements.length; i++){if(element[i].nodeType==1&& element[i].hasAttribute('selected'){// first test for valid node type ...break;// break out of the loop if we found what we were looking for}}

Avoid modifications while traversing

childNodes and NodeList returned by getElementsByTagName() are live. This means that these collections may change without waiting for the execution to finish first.

If new elements are added to the collections while they are traversed, an infinite loop may occur.

If new elements are added even outside of collection itself, the collection must look for potential new entries. Due to this it cannot remember its last position or length which need to be recalculated.

Cache DOM values into variables

Cache frequently accessed DOM values into variables.

Slow:

document.getElementById('elem').propertyOne='value of first property';document.getElementById('elem').propertyTwo='value of second property';document.getElementById('elem').propertyThree='value of third property';

Faster:

var elem = document.getElementById('elem').propertyOne='value of first property';elem.propertyTwo='value of second property';elem.propertyThree='value of third property'

Remove references to documents that have been closed

If a reference to frame, iframe or object is stored in a global variable or a property, clear it by setting it to null or deleting it.

For example, a popup window that is closed and has a reference to global variable, will be kept in memory although the document itself is no longer loaded, if it is not deleted manually.

Consider using a custom data exchange format for large datasets, as an alternative to XML and JSON

Depending on the browser, its version and OS, the performance difference between XML or JSON parsing and traversing may be very significant. For example, on Nokia 5800 XpressMusic with a very large dataset JSON is approx. 5x faster.

Using a custom string-based data format (think CSV) over XML and JSON is efficient in terms of transferred bytes and parse time with large datasets.

Using JavaScript's String and RegExp methods one can match the speed of JSON executed natively with as little overhead to the file size as possible.

Animations

Use animations modestly

Animation without hardware support is slow. Try to avoid excessive use of animations which do not bring any real usability value. At least give users an opportunity to disable animations.

Use scrollTo() to animate scrolling

Using native scrolling via scrollTo() performs significantly better as it does not trigger reflow.

Absolutely or fixed position animated elements

By default elements have style property position: static and animating such elements causes reflowing of the layout and is expensive.

Elements to be animated should be set as position: absolute or position: fixed if reflowing is not mandatory for smoother animation and lesser CPU load. Such elements do not affect other elements layout, so they will only cause a repaint rather than a full reflow with a nice performance boost.

CSS positioning schemes for position property:

Position value

Description

static

Default. An element with position: static always has the position the normal flow of the page gives it (a static element ignores any top, bottom, left, or right declarations)

relative

An element with position: relative moves an element relative to its normal position, so left:20 adds 20 pixels to the element's left position

absolute

An element with position: absolute is positioned at the specified coordinates relative to its containing block. The element's position is specified with the left, top, right, and bottom properties.

fixed

An element with position: fixed is positioned at the specified coordinates relative to the browser window. The element's position is specified with the left, top, right, and bottom properties. The element remains at that position regardless of scrolling.

Use one timer to animate multiple elements at the same time

setTimeout() and setInterval() timers are two basic methods used to implement animation (i.e. trigger changes to element size, position and/or appearance over time).

If multiple elements are animated at the same time, the best frame rate is achieved by iterating across all animated elements inside a single loop. Using multiple timers makes animation less efficient and consistent, presumably due to timer invocation overhead.

Trade animation smoothness for speed

To trade smoothness for speed means that while you may want to move an animation 1 pixel at a time, the animation and subsequent reflows may in that case use 100% of the CPU and the animation will seem jumpy as the browser is forced to drop frames to update the flow. Moving the animated element by e.g. 5 pixels at a time may seem slightly less smooth on faster machines, but won't cause CPU thrashing that easily on mobile devices.

Events

Use event delegation

Assigning event handlers to individual objects can add up quickly and is expensive if you create a lots of new elements dynamically to which event handlers need to be bound.

This becomes especially interesting if you assign multiple event listeners (e.g. click and blur to a one element. In case of 100 elements that would mean 200 event handlers.

Using DOM Level 2 event model all events propagate toward the document object which is highest up in the hierarchy. This means that one can bind event listeners to document which invokes a controller and passes the event object to it. The controller is responsible for inspecting the internals of the event and dispatching to appropriate logic.

Throttle event handlers which fire excessively

If a handler is called many times, the responsiveness of the UI degrade and tops the CPU. This is especially an issue if the event handler triggers reflow as is the case with resize.

On S60 5.0 devices input element's blur handler is called each time the content of the input field changes, e.g. user types in a single letter.

You may want your function to run once after the last event has fired to prevent excessive calls to potentially expensive functions. You must implement throttling mechanism to achieve that kind of a rate limited.

Escaping the JavaScript call stack with setTimeout

Setting up handlers that run after an event has fired needs some trickery. Because the event doesn't take effect until the event-handling call stack opens and closes completely, there's no way to work in a post-event environment via conventional event handlers.

When something is called using setTimeout with a delay of 0 the JavaScript engine notices it is busy (with a task which invoked setTimeout) and queues the setTimeout code for execution immediately after the current call stack closes.

This technique can be used to prioritize certain functionality such as showing and hiding loading indicator prior to executing a computationally heavy operation such as modifying the DOM.

In a typical scenario this will not turn the loading indicator visible:

showLoadingIndicator(); doSomethingExpensive();

Workaround is to use setTimeout as follows (please take extra care when using this anti-pattern as what it does is actually delays the execution in order to display the loading indicator in the UI):

Change CSS classes near the edges of the DOM tree

To limit the scope of the reflow to as few nodes as possible you should avoid changing a class on wrapper (or body) element(s) which affects the display of many child nodes. Additionally, that may result in re-resolving style on the entire document and for a large DOM that could lock up the browser for a while.

Avoid tables for layout or use table-layout: fixed

Avoid tables for layout. As if you needed another reason to avoid them, tables often require multiple passes before the layout is completely established because they are one of the rare cases where elements can affect the display of other elements that came before them on the DOM. Imagine a cell at the end of the table with very wide content that causes the column to be completely resized. This is why tables are not rendered progressively in all browsers and yet another reason why they are a bad idea for layout.

It is recommended to use a fixed layout ( table-layout: fixed) for data tables to allow a more efficient layout algorithm. This will allow the table to render row by row according to the CSS 2.1 specification.

S60 specifics

Styling

Remove borders around focusable elements

In WRT 1.x you can remove default blue borders in tab navigation mode with the following CSS. a:hover, a:focus { outline: none; }

Fix focus problems in tabbed navigation mode

In tabbed navigation mode the focus may lost while switching views. A fix to this issue is to place a read-only <input> text element (can be absolutely positioned, zero-sized and transparent) under the element to be focused after switching the view and focus():ing on it after the view changes.

Allow animated gif images to animate after being hidden

S60 3.x and 5.0 devices do not animated gif images if they have been set as display: none at any point during their lifespan regardless of subsequent display value. A workaround to this bug is to avoid display: none and use position: absolute and alter left property to move the animated gif outside of the visible viewport to hide it ( left: -100%) and bring it back ( left: 0).

Alternatively you can fold the animated gif into another element whose style you set in a similar fashion.

When you get an onhide event, call clearInterval() and clearTimeout() for any pending timers and abort any pending XHR calls to keep the CPU usage low.

Use setInterval wisely

Animations on Home Screen

Avoid animations on home screen unless they are really needed for the sake of good user experience.

Misc

Avoid background-repeat if the background-image is small relative to its containing element

Interaction such as scrolling is extremely slow if background-repeat property is set to repeat-x or repeat-y combined with a background-image of a size which needs to be repeated multiple times to fill in the element's background. A workaround is to avoid using repeating backgrounds i.e. use a big enough background-image with background-repeat: no-repeat. For example, a commonly used CSS design pattern -- which degrades the performance and should be avoided -- is to create a continuous vertical gradient background using a background image of width 1px which is repeated horizontally across the whole element width.

This optimization pattern is a tradeoff between the initial load time and the subsequent interaction smoothness. In a typical scenario using a bigger background-image (longed load time) with background-repeat: no-repeat delivers a far superior overall UX over using a smaller repeating background.

Minimize the use of slow jQuery methods

remove(), hmtl() and empty() have order of n2 time complexity, that is T(n) = O(n2). Minimizing the use of these methods is recommended in jQuery versions prior to 1.3.3. .

append, prepend, before, after methods are expensive as well as taking a chunk of HTML and serializing it ($("

Hello World!

")).

The process behind these manipulations methods is the following: cleaning the input string, converting the string into a DOM fragment and injecting it into the DOM.

Optimize selectors

Pay attention to the selectors you use. E.g. if you want to hide paragraphs which are direct children of div elements there are multiple ways to do it.

Using the selector below will try to find all div elements, loops thought all of them and find all p relative to the div, merge and figure out unique results. Making sure that the returned set is unique is where the most time is spent, and is very slow especially with a large DOM tree.

Slow:

$("div p").hide();

A one way to optimize the selector is to match direct children of div (this may require you to re-factor the DOM structure of your app and may not be feasible in every scenario). This alternative flatter selector will try to find all div elements, loops thought all child elements and verifies if element is p.

Faster:

$("div > p").hide();

Consider alternatives to jQuery.each()

Depending on the size of the array, looping through it using for (i = 0; i < len; i++) instead of jQuery.each(myArray) may be faster.

Consider alternatives to .show(), .hide() and .toggle()

Toggling element visibility via .css({'display':'none'}) and .css({'display':'block'}); is faster than using convenience functions .show(), .hide() and toggle(), especially while working with large number of elements. Depending on the rendering engine used, .css() is also faster than using .addClass() and .removeClass().