Microsoft Teched EMEA 2008 Developers Summary – High Performance with JavaScript

What are the top five things that can cause your code to run slowly in the browser? With the right optimization, JavaScript can run quickly and efficiently in the browser environment.

Optimizing Symbol Resolution is the process of identifying the value associated with a name or symbol, namely the process by which JavaScript identifies a variable or function that you are referring to. By understanding how this is performed in the background, you can write better code.

The identification process is performed by a so-called scoping.

 

The JavaScript engine starts the search of the variable or function first in the local scope, which is the function currently being executed. If it doesn’t find the symbol in the local scope, it then searches it in the global scope, which is everything outside of any function. The next step where the search will continue is the DOM. The browser looks into its build-in methods and properties and if it doesn’t find it there, then it looks finally in the expandos, which are the custom properties and methods that the developer has added to the DOM. That’s the most expensive call.

Example 1

Background: evaluating local variables

   1:  function WorkOnLocalVariable()
   2:  {
   3:          local_variable = ObtainValueFromDOM();
   4:          return (local_variable + 1);
   5:  }

Problem: this local variable hasn’t been declared within this function explicitly with var. So the JavaScript engine doesn’t know that it is only a local scope variable and it is going to walk through the entire chain before realizing that it is in local scope.

Recommendation: declare local variables by adding the var keyword so that you explicitly tell the JavaScript engine that this is a local variable that you are working on.

   1:  function WorkOnLocalVariable()
   2:  {
   3:          var local_variable = ObtainValueFromDOM();
   4:          return (local_variable + 1);
   5:  }

Example 2

Background: recognize implicit look ups

   1:  function BuildUI()
   2:  {     
   3:        var baseElement = document.getElementById(‘target’);
   4:        baseElement.innerHTML = ‘’; // Clear out the previous
   5:        baseElement.innerHTML += BuildTitle();
   6:        baseElement.innerHTML += BuildBody();
   7:        baseElement.innerHTML += BuildFooter();
   8:  }

Problem: the innerHTML call are multiple calls into the DOM which will negatively affect performance.

Recommendation: you can improve this by caching the values in local variables and staying within the JavaScript engine. Then the access to DOM is taking place only once at the end.

   1:  function BuildUI(){
   2:        var elementText = BuildTitle() + BuildBody() + BuildFooter();
   3:        document.getElementById(‘target’).innerHTML = elementText; 
   4:  }

Example 3

Background: Multiple DOM Look-ups

   1:  function CalculateSum()
   2:  {
   3:        var lSide = document.body.all.lSide.value;
   4:        var rSide = document.body.all.rSide.value;
   5:        document.body.all.result.value = lSide + rSide;
   6:  }

Problem: this involves three DOM look-ups of the same property.

Recommendation: cache values in local variables so that it only call’s the DOM once.

   1:  function CalculateSum()
   2:  {
   3:        var elemCollection = document.body.all; // Cache this
   4:        var lSide = elemCollection.lSide.value;
   5:        var rSide = elemCollection.rSide.value;
   6:        elemCollection.result.value = lSide + rSide;
   7:  }

Example 4

Background: JavaScript function symbolic lookup

   1:  function IterateWorkOverCollection()
   2:  {
   3:        var length = myCollection.getItemCount();
   4:        for(var index = 0; index<length; index++)
   5:        {
   6:              Work(myCollection[index]);
   7:        }
   8:  }

Problem: the function is called within a loop so the JavaScript engine has to look up what that function is.

Recommandation: cache JavaScript function pointers

   1:  function IterateWorkOverCollection()
   2:  {
   3:        var funcWork = Work;
   4:        var length = myCollection.getItemCount();
   5:        for(var index = 0; index<length; index++)
   6:        {
   7:              funcWork(myCollection[index]);
   8:        }
   9:  }

Example 5

Background: IE function symbolic lookup

   1:  function IterateWorkOverCollection()
   2:  {
   3:     var parentElement = document.getElementById(‘target’);
   4:     var length = myCollection.getItemCount();
   5:     for(var index = 0; index<length; index++)
   6:     {
   7:        parentElement.appendChild(myCollection[iterate]);
   8:     }
   9:  }

Problem: the IE function is called multiple times within a loop causing multiple DOM access.

Recommendation: cache IE function pointers

   1:  function IterateWorkOverCollection()
   2:  {
   3:     var funcAppendChild = document.getElementById(‘target’).appendChild;
   4:     var length = myCollection.getItemCount();
   5:     for(var index = 0; index<length; index++)
   6:     {
   7:        funcAppendChild(myCollection[index]);
   8:     }
   9:  }

How can you improve your code regarding optimizing symbol resolution? Here are some takeaways:

  • Closely examine loops
  • Closely examine DOM access
  • Consider maintainability

JavaScript Coding Inefficiencies

Example 1

Background: string concatenation

   1:  var smallerStrings = new Array();
   2:  var myRatherLargeString = "";
   3:  var length = smallerStrings.length;
   4:  for(var index = 0; index<length; index++)
   5:  {
   6:      myRatherLargeString += smallerStrings[index];
   7:  }

Recommendation: avoid using string concat in IE6 and IE7 because of bad performance caused by allocating a new chunk of memory and performing two string copies. Use array.join instead of string concatenation.

   1:  var smallerStrings = new Array();
   2:  var myRatherLargeString = "";
   3:  smallerStrings.push("
"); 6: smallerStrings.push("sampleText"); 7: smallerStrings.push("
"); 8: … 9: var myRatherLargeString = smallerStrings.join(‘’);

Here are some general IE7 recommendations regarding the JavaScript coding inefficiencies:

  • Perform string operations using local vars
  • Cache strings from IE objects
  • Use Array.join for concatenation

IE8 is improved with the followings:

  • Strcat is faster for large and small strings – better than Array.join
  • Strings only flattened when passed to DOM
  • Str calls may flatten strings
  • Array lookup is faster
  • Array.Push & Array.Pop are faster

Example 2

Background: costs for using EVAL

Everytime the JavaScript engine hits an Eval, it creates a new script execution context, a new instance of the parser and causes a lot of overhead. The Eval has great implications also regarding security because Eval executes whatever JavaScript is in that string. So if someone else has tampered with the data, then he can run malicious JavaScript within the global context of your application. It is recommendable to use a third-party JavaScript library that sanitizes the data.

IE8 has a new built-in global object within the JScript engine for this purpose, called JSON. By using the JSON.parse() and JSON.stringify(), you can make sure that the only thing in the string is JSON rather than any executable script.

Example 3

Background: SWITCH

Switch statements a great way to make the code look clean and easily readable, rather than using a large number of if else statements. The JavaScript engine does not optimize switch statements, instead it turns the switch statements into if else statements. The problem with switch occurs when it is used with large sets because its processing time grows linearly. The workaround to fix this issue is to implement your own hash table wrapped in a try catch.

IE Performance Considerations

Recommendation: Minimize DOM/JavaScript interaction because of costly DOM function instantiation or DOM property access. Instead, cache function pointers and values locally, if you are calling it repeatedly.

Recommendation: For traversing sibling nodes, use nextSibling rather than the direct index of the child nodes array. It is significantly faster

   1:  function bestLoop(div1) 
   2:  { 
   3:      for(node = div1.parentNode.childNodes[0]; 
   4:           node != null; node = node.nextSibling) 
   5:      { 
   6:      alert(“John’s code Rocks!"); 
   7:   
   8:      }; 
   9:  }

Recommendation: by accessing DOM, the DOM methods are faster to get items than innerHTML. GetElementByID is better than walking the HTML and IE8’s QuerySelectorAll is better than GetElementByID.

 

HTTP Performance

We have two considerations regarding the HTTP performance, the first-time users and the subsequent users.

The first is when the user visits your website the first time or the first time in a long while. This is the case when you can’t depend on the cache but there are ways to optimize performance. One way would be the request from the server and the other one within the browser. Let’s focus on the first option, because we have already discussed the browser in detail.

Recommendation: First Visit – Simplify & Reduce, by reducing the number of HTTP sessions that your website uses, reducing the downloaded content’s size, for example: have fewer smaller unscaled images so save bandwith, use the CSS sprites technique to combine all the images into one big jpeg file and then, instead of using image tags, you create divs using the CSS background property to position this image right underneath your div. It is a great way to use far fewer HTTP requests and yet still use many different images.

Recommendation: Put script in one JS file linked at end of HTML and styles in one CSS file linked at beginning of HTML to minimize the number of HTTP requests that your website performs.

First of all, why just not inline the scripts in markup then? The answer is to re-use the same scripts in multiple pages. So if you point all your webpages to the same JavaScript file, the browser caches that script file and when the next pages re-use the same script file, they don’t have to download the same script. If all those scripts are inline, then they need to be downloaded for each page every time and it doesn’t give you the ability to take advantage of the cache.

Second, why should you link the JavaScript file to the end? When the browser hits a script tag, it stops downloading and parsing the rest of the page to minimize the time spent because it thinks that the script file could contain document.write which will undo the already spent CPU cycles. If the script files not containing document.write will be linked to the end of the page, the browser will progressively render everything on the page. This way, the user will not stare at an empty page and then have all the content appear at once after some time. IE8 though has increased the number of concurrent connections of a domain and takes advantage of it by doing speculative download, which downloads the scripts files anyway.

Regarding the CSS file being linked at the beginning of the page, when the browser hits an element with a non-existing style, it won’t display anything because it wants to avoid shortly flashing of non-styled elements and then the same elements with the styles applied on it. So the best practice is to link your CSS files to the beginning of the page so the browser has all the information it needs to keep progressively rendering the HTML as it goes through it.

Recommendation: Use HTTP Compression with gzip for all your requests, when necessary.

In case of subsequent users who are coming back to your website frequently, make sure that in your HTTP requests and responses you take advantage of the cache appropriately. You can do this by using the time conditional cache If-modified-since or by providing time conditional cachable content with Expires and Max-age. You still have to do the HTTP request and response but you can avoid unnecessary content download.

Helper tools should be used to understand the performance implications of your design by understanding the system. For IE, you can take advantage of the Fiddler and IE8’s Developer Tools with the Profiler and, for Firefox, you can use Firebug and YSlow.

Here are some final tips for solving performance issues or optimizing your website:

Identify the performance bottleneck

      • Network / Bandwidth – using Fiddler
      • JavaScript – using Developer Tools
      • Aggressive DOM access – using Developer Tools

Reduce, Simplify, Re-factor

      • Reduce the bytes sent between the client/server
      • Simplify your code to avoid costly JavaScript constructs
      • Cache DOM properties and function pointers

These recommendations have to be evaluated regarding how they apply for your scenario and use them in your own context accordingly. So understanding trade-offs is important. 

And finally, here is a summary of script and performance considerations:

  • Optimize JavaScript symbol look up for speed
  • Understand JavaScript string costs
  • Consider DOM interaction costs
  • Optimize your app to read from the HTTP cache

~~~~~~~~~~~~~~~~~~~~~~

Referenced sessions:

  • WUX315 – Developing High Performance Javascript AJAX by John Hrvatin

Resources: