DHTML and JavaScript Performance Tuning

Using JavaScript

Objects

The way objects are used can have a dramatic impact on performance. Long object chains, large hashtables, repeated references, and brute-force object initialization can hinder performance.

Here are some way to avoid these problems:

  1. Shorten Object Chains
  2. Avoid Expando Properties
  3. Use Custom Objects
  4. Cache Frequently Used Objects
  5. Use Lazy Initialization
  1. Shorten Object Chains

    Shorten Object Chains
    Ease Effectiveness Overall Practicality
    7
    M IE S Op
    10 10 9 ?
    8

    An example of an object chain in Javascript:

    window.document.forms[0];
    

    Each dot in the object reference results in a lookup for that object.

    If the object chain is referenced more than once, we can improve performance by introducing a temporary variable representing the longest common chain.

    for example:

    function setAutumnTheme() {
    	if(typeof document.body != "undefined") {
    		document.body.style.color = "#330000";
    		document.body.style.backgroundColor = "#ffffcc";
    	}
    }
    

    Can be further reduced to:

    function setAutumnTheme() {
        // Create a shortened object chain.
        var body = document.body; 
        if(typeof body != "undefined") {
            body.style.color = "#330000";
            body.style.backgroundColor = "#ffffcc";
        }
    }
    

    Do you see any other object chains that are used more than once? (Hint there is one more).

    Demo ->

    Object Chains Demo

     

  2. Avoid Expando Properties

    An expando property is an arbitrary property that can be added to any object. When an expando property is added, the property is searched through that object's lookup table. When we create custom objects, the overhead in searching through a large table, however slight, is avoided.

    Expando properties will make the code less clear. By using clearly defined custom objects, the program becomes easier to understand. Remember, code that explicit is tunable.

    1. Demo ->

      Window Properties vs. Custom Object Properties

       

    2. Demo ->

      "Keep Your Expando Properties off document"

      (This demo is a variation of a demo provided by Microsoft™)

    The proper alternative to using expando is the subject of the next topic: Custom Objects.

  3. Use Custom Objects

    Speed up animation with custom objects. For the following examples, we will refer to the following custom object, which is optimized for fast performance.

    ElementWrapper = function(el) {
        this.el = el;
        this.id = el.id;
        this.css = this.el.style; // shorten object chain.
    
      // use parseInt here once to avoid repeated calls in moveTo.
        this.x = parseInt(this.css.left); 
        this.y = parseInt(this.css.top);
        ElementWrappers[this.id] = this;
    };
    

    When accessing and setting the position of an element, most developers use the element.style.left and element.style.top properties. This is inefficient because the left and top properties, like all other CSS properties, are returned as a string. If we want to do math on the returned value, it first needs to be converted from a string to an integer by using parseInt. Here is an example:

    ElementWrapper.prototype = {
    /**
    * Moves the element to a pixel coordinate.
    * This works, but is not optimally efficient.
    */
        moveTo : function(x, y) {
    	var left = parseInt(this.css.left);
    	var top = parseInt(this.css.top);
      
    	this.css.left = left + x + "px";
    	this.css.top = top + y + "px";
        },
    ...
    };
    

    By using a custom object as a wrapper and by inlining assignment statements, better performance can be achieved in any browser.

    ElementWrapper.prototype = {
        moveTo : function(x, y) {
            // inlining assignment for faster performance.
            this.css.left = (this.x = x) + "px";
            this.css.top = (this.y = y) + "px";
        },
    ...
    };
    

    To store the ElementWrapper objects, we can use a custom lookup table, such as this:

    ElementWrappers = { };
    

    The ElementWrappers object will serve as a cache to store ElementWrapper instances. This brings us to the next topic: caching.

  4. Cache Frequently Used Objects

    Creating a new object is more expensive than referencing a cached object.

    Consider creating a regular expression pattern, or RegExp.

    Function getTokenizedExp will create a regular expression based on the parameters.

    RegExp Function (no cache)

    /** 
     * Return a regular expression based on the parameters.
     * token - a string that is delimited by whitespace or string 
     *         boundary points 
     * flag  - a regexp flag. example:
     *          i - case-insensitive
     */
    function getTokenizedExp(token, flag) {
        return new new RegExp("(^|\\s)"+token+"($|\\s)", flag);
    }
    

    It works, and it's faster than using String methods like indexOf and substring, but if our program calls the function with the same parameters more than once, the function will recreate a regexp, wasting CPUs.

    What if we could have a function that would return a RegExp, but only create it if it hasn't already been created.

    The function will first look in the cache to see if the RegExp has been created. If the RegExp is in the cache, it will just return that RegExp. Otherwise (the RegExp is not in the cache) the function will create a new RegExp, store it in the cache, and then return it.

    Here's how to realize that in code:

    RegExp Function With a Cache

    TokenizedExps = { };
    function getTokenizedExp(token, flag){
        var x = TokenizedExps[token+flag];
        if(!x)
            x = TokenizedExps[token+flag] = 
                new RegExp("(^|\\s)"+token+"($|\\s)", flag);
        return x;
    }
    
    /**
     * tests to see if String s contains token token.
     */
    function hasToken(s, tok){
        return getTokenizedExp(tok,"").test(s);
    };
    

    Here is a demo of the example code in action:

    Demo ->

    Regular Expression Cache
  5.  

    The same principal for caching RegExp objects can also be applied to custom objects, such as ElementWrapper instances.

    Demo ->

    Custom Object Cache

     

  6. Use Lazy Initialization

    Also called "initialize on demand", lazy initialization is a way of creating objects only when they are needed. Instead of initializing everything when the page loads, lazy initialization uses some event, such as a "click" or "mouseover" to initialize the object.

    Lazy initialization and object caching are two techniques that tend to go hand in hand.

    In JavaScript, the object's getInstance method is usually called in a function that is called from an event handler. It usually looks like this:

    function handleClickedDiv(el) {
        var ew = ElementWrapper.getInstance(el.id);
    
        // do something with ew.
    }
    

    Initializing many objects in onload can make the browser noticeably unresponsive for a moment. The user perception will be greater than the actual performance hit, which is often less than a second!

    Benefits of Using Lazy Initialization in JavaScript

    • create objects only when needed
    • same object never created twice
    • Eliminate initialization overhead

    Here's how we can define a getInstance function:

    ElementWrappers = { };
    
    ElementWrapper.getInstance = function(id) {
        var x = ElementWrappers[id];
        if(x == null)
            x = ElementWrappers[id] = 
                      new ElementWrapper(document.getElementById(id));
        return x;
    };
    

    This technique can be used with custom object instances, as I have shown here, or it can be used with native object instances. The demos in item 4 show the benefits of of using cached regular expressions compared to using non-cached regular expressions.

Next in this tutorial:

 

*AnimTree
*Tabs
*GlideMenus
*DragLib