JavaScript Performance Tips

Asv3's Blog

  • avoid loops

  • avoid unused code

  • avoid loging

  • using strict mode

  • use === instead of == (browser has more clarity)

  • exploit hoisting

  • use hasOwnProperty

  • use local references for values that have deeper resolution path

  • avoid synchronous calls

  • minimize dom updates

  • minimize style updates

  • use fragment whenever applicable

  • avoid (for i in) on objects

  • exploit power of mapping

  • have a check on prototypal inheritance

View original post


Story of DataTable

Many times I had chance to work on DataTable which in fact most complicated widget you can think of. Every time  my preference of stack were depending on the company and team. In all I had opportunity for

1. Extending existing DataTable in YUI3 and adding Sorting/Pagination/Filter/Scrolling functionalities using extensions and plugins
2. Created a DataTable component using Backbone which had futures like Sorting and Filter
3. Created a DataTable component using CanJS which was heavily using EJS template with live binding.
4. Again working DataTable component using Backbone with different approach, planning to have all possible features to it.

While working on these stack, I figured out that DataTable component has it’s own unique challenges, and implementing features like sorting, pagination, filters, on-demand dataloading, scrolling, inline edits, expandable detail views etc will increase depth and breadth of your JavaScript knowledge. Here I am listing challenges I faced and how did I encounter them.

Normally below are the features expected in DataTable

1. Pagination with rows per page drop-down, with first, last, next, previous page links, go-to box, linked page numbers
2. Sorting, most probably single column sorting
3. Filters, table level filter or column level filter
4. In-Line edit for certain columns
5. Column formatters, currency, date, number and link formats
6. Show/Hide columns
7. Scrolling, horizontal scrolling with pagination or vertical scrolling without pagination
8. Expandable Detail Views
9. On-Demand data-loading

 

…more to come


Golden Rules of JavaScript SPA development

I have been working on a Backbone based Single Page Application since last 4-5 months, I can say this was the most challenging UI assignment I have been part of. In the process, I followed certain rules/guidelines across project, which helped me handle unique challenges of SPA better. Here I am listing some of them for the benefit people who have just started working on SPAs.

At Widget Level
———————
1. Should have minimal defaults configured, widget should be up and running with no/minimal configuration.
2. Input/Configuration should always be an “Object”, with this approach adding new parameter will never disturb existing implementation, and let widget scale as needed.
3. Plan loading, loadSuccess, loadFailure, dataOverflow, dataUnderflow and noData cases from the beginning. Most of the widgets need these phases to be implemented, adding them sooner is better.
4. Every widget should load it’s own data, but through a app level common gateway function.
5. Don’t assume data/script load delays, cached calls would returned in the same execution thread without any delay. This was cause behind most intermittent/difficult to reproduce bugs
6. Use deferreds to handle async callbacks. This provides the flexibility of adding callbacks even after initialization/completion of async call, and guarantee execution irrespective of call status. With deferreds no special code needed to handle “async calls resolved with 0 delay”.
7. Always keep Model and View seperate but in sync.

At Application Level
—————————-
1. Maintain a request Index with cache, abort and queue configurations for all requests you make, and handle them in common gateway function. In run time you just make request with id and params.
2. Follow same JSON format for all requests, there should be a common parser for all requests.
3. Every form needs pre-post parser, validator, reset functionalities plan them before hand especially while giving estimates.
4. Be careful while adding events for body (ex: click-outside), removing source view doesn’t clear the event listener.
5. Most of the widgets or nothing but single/multi select lists, having base class that support these functionality and re-using them saves lots of time.
6. If needed plan for Internationalization up front, rather than later.


Rooted Tree implementation in JavaScript

Here is an example of Rooted Tree implementation;

var TreeNode = function TreeNode (data, parent) {
	this.data = data;
	this.parent = parent;
}

TreeNode.prototype.preOrder = function(){
	this.visit();

	if(this.firstChild){
		this.firstChild.preOrder();
	}

	if(this.nextSibling){
		this.nextSibling.preOrder();
	}
}

TreeNode.prototype.postOrder = function(){
	
	if(this.firstChild){
		this.firstChild.postOrder();
	}

	this.visit();

	if(this.nextSibling){
		this.nextSibling.postOrder();
	}
}

TreeNode.prototype.getLastSibling = function() {
	if(this.nextSibling){
		return this.nextSibling.getLastSibling();
	}else{
		return this;
	}
};

TreeNode.prototype.visit = function(){
	console.log(this.getData());
}

TreeNode.prototype.getData = function() {
	if(this.parent){
		return this.parent.getData()+'-'+this.data;
	}else{
		return this.data;
	}
};

var Tree = function Tree (root) {
	this.size = 0;
	this.root = root;
}

Tree.prototype.addNode = function(node, parent) {
	if(!parent){
		parent = this.root;
	}

	node.parent = parent;
	if(!parent.firstChild){
		parent.firstChild = node;
	}else{
		parent.firstChild.getLastSibling().nextSibling = node;
	}
	tree.size++;
};

Usage

var treeHeight = 8;
var root =  new TreeNode('root', null);

var tree =  new Tree(root);
for(var i=0; i<treeHeight;i++){
	var node = new TreeNode(i+'');
	

	for(var j=0; j<i;j++){
		var node2 = new TreeNode(i+''+j);
		tree.addNode(node2, node)

		for(var k=0; k<j; k++){
			tree.addNode(new TreeNode(i+''+j+''+k), node2)
		}
	}

	tree.addNode(node);

}

root.preOrder();
root.postOrder();

Coding guidelines for JavaScript Starters

It’s not a complete list, I am just listing stuff that I thought overlooked.

1. Keep the code readabble, Indent well, try using some editor which supports auto-indent.

2. Keep function/method names descriptive. Don’t try to save bytes here, if it is a handler call out what that handler handles. Saving bytes can be easily done in minification, you don’t have to do that while writing code.

3. JSHint/JSLint your JS code before commiting to git, most probably your code will get concatinated with other JS files in near future, JSHint/Lint will make sure your code live well with others’. There are some editors which support “check as you type”.

4. Comment as much as possible. We are yet to agree on a company wide JS documentation tool/standard, but commenting now will help us when we agree and adopt one. It will also help you to remember reasons behind design decisions when you come back later.

5. When concluding on the solutions, always ask “Does it scale?”. We are moving towards SPAs every where and whole applications will get rendered/managed in single page, if your solution doesn’t scale there is no point in having it.

6. Keep model and view seperate, If we are using Backbone, CanJS or any MV* framework, we choose it for a reason,that is keeping model and view separate. Thumb rule is, interaction on one dom element should not update another dom element directly, flow should be dom-element1=>model and then model=>dom-element2

7. Keep your methods/functions small, if you have to write more than 2 lines to describe what that function does, that’s an indication that one function needs some splitting.

8. Adopt DRY as much you can, if you are copy pasting between modules, there is some thing wrong in you approach.

9. Add Event listeners only when needed. make sure you remove all listeners you added

10. When passing more than 1 arguments, pass it as argument object, this will let you scale function easily, also it’s easy to write fallback for missing argument

11. If widgets/components have dependency for data, make sure you handle loading state, letting user know that data is being loaded is very important

12. If you are combing dom elements to read the state of a widget, this could become slow as your application scale, always maintain a Model, whenever data needed out of widget, read it from Model, while updating update both model and view.

13. Minimize DOM queries, use local variables whenever you make same query multiple times.

14. Keep an eye on what is getting added to global namespace, lesser is better here.

Finally if you have not yet read “JavaScript Good Parts (from Douglas Crockford)”, get a copy right away, and read it once. You should be able to finish that in couple of hours, but it will save loads of debugging time for you in future. If you are coding JS you should know what you should not do, and this book just tell you that.


JavaScript to generate lorem ipsum data


var lorem = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";

var loremArray = lorem.split(/s/);

function getWordlist(count, wordcount) {
    var toreturn = []
    var loremlength = loremArray.length
    wordcount = wordcount || 1;
    for (var i = 0; i < count; i++) {
        var s = []
        for (var j = 0; j < wordcount; j++) {
            s.push(loremArray[Math.ceil(Math.random() * loremlength)]);
        }
        toreturn.push(s.join(' '));
    }
    return toreturn;
}

function logArray(arr) {
    for (var i = 0, l = arr.length; i < l; i++) {
        console.log(arr[i]);
    }
}
logArray(getWordlist(10, 3))
logArray(getWordlist(10, 1))

Mixin Pattern

var Selectable = function () {};

Selectable.prototype = {
    isSelected: function () {
        return this.selected || false;
    },
    setSelected: function (boolean) {
        this.selected = boolean;
    },
    toggleSelected: function () {
        this.selected = !this.isSelected()
    }
}

function augment(givingClass, receivingClass) {
    // if copying selected properties
    var methodOrProperty;
    if (arguments[2]) {
        for (var i = 0, len = arguments[2].length; i & lt; len; i++) {
            methodOrProperty = arguments[2][i];
            receivingClass.prototype[methodOrProperty] = givingClass.prototype[methodOrProperty]
        }
    } else {
        //copy all properties
        for (methodOrProperty in givingClass.prototype) {
            if (!receivingClass[methodOrProperty]) {
                receivingClass.prototype[methodOrProperty] = givingClass.prototype[methodOrProperty]
            }
        }

    }
}

var ListItem = function () {
    this.selected = false;
}

augment(Selectable, ListItem);
var item1 = new ListItem();
console.log(item1.isSelected()); //false

Observer pattern implemented in vanilla JavaScript

Ever wondered how custom events works in JavaScript, it’s a very simple implementation of Observer pattern, here is a raw example written in vanilla JavaScript, popular libraries like Jquery and YUI3 has more polished implementations where you can unsubscribe without tokens, by functions/event names, but at heart this is what happens in background.

var Observer = {};

(function (ob) {

var subscribeToken = -1;

var subscriberIndex = {}

ob.publish = function () {
//type of event is the first argument
var type = arguments[0]
//rest of arguments will be used as arguments for handler execution
var args = [].slice.call(arguments, 1)

if (!subscriberIndex[type]) {
return false;
}

var subscribers = subscriberIndex[type];

var len = subscribers ? subscribers.length : 0;

while (len--) {
subscribers[len].func(type, args)
}

//console.log(type, args)

}

ob.subscribe = function (type, handler) {
if (!subscriberIndex[type]) {
subscriberIndex[type] = []
}

var token = (++subscribeToken).toString()

subscriberIndex[type].push({
func:handler,
token:token
})

return token;
}

ob.unsubscribe = function (token) {
for (var type in subscriberIndex) {
var subscribers = subscriberIndex[type];
for (var i = 0; i &lt; subscribers.length; i++) {
var subscriber = subscribers[i];
if (token === subscriber.token) {
subscribers.splice(i, 1)
return token;
}
}
}
return this;
}

})(Observer)

var token = Observer.subscribe('test', console.log)

Observer.publish('test', {test:'test'}) //test [Object { test= "test" } ]

Observer.unsubscribe(token)

Observer.publish('test', {test:'test'}) // nothing happens


Singleton Creation pattern example

Notes:

  • Singleton is immediately executed function
var log = console.log;
var Singleton = (function (){
var instance;

var init = function (){
var privateVar = 'this is secret';

var privateMethod = function (){
return privateVar;
}

return {
publicMethod : function (){
return privateMethod();
},
publicVar : 'this is public'
}
}

return {
getInstance : function (){
if(!instance){
instance = init();
}
return instance;
}
}
})()

var singletonInstance = Singleton.getInstance()
log(singletonInstance.publicMethod()) // this is secret
log(singletonInstance.publicVar) // this is public
log(singletonInstance.privateMethod()) //Error

Simple example illustrating private and public properties for JavaScript class

var log = console.log;
var Class = function (){</pre>
var privateVar = 'this is secrete';

var privateMethod = function (){
return privateVar;
}

return {
publicMethod: function (){
return privateMethod()
},
publicVar :'this is public'
}
}

var instance = new Class()

log(instance.publicMethod()) // this is secret
log(instance.publicVar) // this is public
log(instance.privateMethod()) //Error