Monday, November 2, 2015

Best Practice: Executions & Memory Control in JavaScript

Control the Memory & Scripts Execution speed

The less operations you do, the more processor time you save, the higher speed you have.
Each new element addition to the DOM cause reflow, which can badly affect the user experience.
Here are few points how to start doing better:
  • re-locate work-intensive scripts;
  • run scripts asynchronously;
  • let inheritance to help with memory efficiency;
  • insert DOM elements within fragments;
  • declare variables as few times as possible;
  • make efficient choices in terms of script execution;

##Re-locate work-intensive scripts

Scripts that are not essential to immediate loading of the page should be moved as low as possible. Move inclusion of your work intensive script in right before the end of the body node. So, the most of the visual and informational components will become available to user before the script is loaded.

<!DOCTYPE html>
<html>
  <head>
    <title>Page Template</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Bootstrap CSS-->
    <link href="css/bootstrap.min.css" rel="stylesheet" media="screen">
  </head>
  <body>
    <h1>Hello, world!</h1>
    <script src="http://code.jquery.com/jquery.js"></script>
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

##Run scripts asynchronously

Use HTML5 async attribute, that will allow the rest of the page to load before the script runs.

<!DOCTYPE html>
<html>
  <head>
    <title>Page Template</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Bootstrap CSS-->
    <link href="css/bootstrap.min.css" rel="stylesheet" media="screen">
    <!-- Async JS-->
    <script src="http://code.jquery.com/jquery.js" async></script>
    <script src="js/bootstrap.min.js" async></script>
  </head>
  <body>
    <h1>Hello, world!</h1>
  </body>
</html>

The browser will fetch the script resources, but will continue parsing and rendering HTML, without waiting for the scripts loading & processing to complete.


##Let inheritance to help with memory efficiency

Watchout with loading the individual entities with code, that might be sourced elsewhere.

Use the static methods & constants for the staff, that might be common for all entities, so it won't create it again & again with each new object instance. Use objects prototype for shared stuff.

Regular object

function Book (title, genre, author){
    this.title = title;
    this.genre = genre;
    this.author = author;

    this.addNote = function(note){
        this.notes = this.notes || [];
        this.notes.push(note);
    } 
}

Share addNote method among the all books.

function Book (title, genre, author){
    this.title = title;
    this.genre = genre;
    this.author = author;
}

Book.prototype = {
    addNote: function(note){
        this.notes = this.notes || [];
        this.notes.push(note);
    } 
}

##Insert DOM elements within fragments

Each new element addition to the DOM cause reflow, which can badly affect the user experience. So, add elements within batch via using DOM fragment.

var navBar = document.getElementById("nav-tabs");
var fragment = document.createDocumentFragment();
for (var i = 0, len = list.length; i < len; i++) {
    var element = document.createElement("li");
    element.appendChild(document.createTextNode(list[i]));
    fragment.appendChild(element);
}
navBar.appendChild(fragment);

##Declare variables as few times as possible

Each var keyword adds look-up for JS parser, that can be avoided with comma extensions.

Before:

var navBar = document.getElementById("nav-tabs");
var fragment = document.createDocumentFragment();

After:

var navBar = document.getElementById("nav-tabs"),
    fragment = document.createDocumentFragment();

##Make efficient choices in terms of script execution.

Regarding the loops see Loops Optimization article;

Regarding the strings: use array join() method to instead of concatenation each element one by one:

page = textLines.join("\n"); 

##Measuring execution time

Here's a small script to measure execution time:

console.time("Benchmark");
page = textLines.join("\n");
console.timeEnd("Benchmark");

Note: The time it takes to run console.time('Benchmark'); will be added to the result time value.

So, it's better to rely on the numerical Time Data:

var startTime = +new Date(); //same as Number (new Date())
page = textLines.join("\n");
var endTime = +new Date();
console.log("Benchmark elapsedTime:" endTime - startTime);

We can even make an util class for this purpose:

function Benchmark(toTest, testParams, repeatNum) {
    this.toTest = toTest;
    this.testParams = testParams;
    this.repeatNum = repeatNum || 10000;
    this.average = 0;
}

Benchmark.prototype = {
    execute: function() {
        var startTime, endTime, elapsedTotal = 0;
        for (var i = 0, len = this.repeatNum; i < len; i++) {
            startTime = +new Date();
            this.toTest(this.testParams);
            endTime = +new Date();
            elapsedTotal += endTime - startTime;
        }
        this.average = elapsedTotal / this.repeatNum;
        return console.log("Average execution time across " + 
                                        this.repeatNum + ": " + 
                                        this.average);
    }
}

Usage:

...
argsList[0] = textLines;
var pageCreationTest = new Benchmark(createPage, argsList)
pageCreationTest.execute();

Average execution time across 10000: 0.0027;


see Also


No comments:

Post a Comment