1 Writing Efficient JavaScript Code Prem Nawaz Khan
Jan 15, 2015
1
Writing Efficient JavaScript CodePrem Nawaz Khan
2
What is this Talk about?
• Not an introduction to JavaScript
• This is about telling you what worked for me and might as well work for you.
• Some collection of ideas and tips over a period of time
3
Optimization
• Premature optimization is the root of all evil. — Donald Knuth
• Do not worry about optimization until you have the application working correctly.
• If it isn’t right, it doesn’t matter if it is fast.
• Clean, correct code is easier to optimize.
• Sometimes restructuring or redesign is required.
4
Some Basics• Make your code understandable
• Strict to PayPal JavaScript coding standards
• Comment as much as needed
• Use shortcut notations
• Allow for configuration
5
Standards
• Valid code is good code
• Good code is secure code (most cases)
• Good code is easy to Optmize
http://webdev.paypal.com/standards/coding-standards/javascript-standards
6
Comments
• Comments are messages from developer to developer
• “Good code explains itself” is a myth
• Comment as much as needed – but no need to do story telling
• // Breaks when line break is removed
• Use /* comments */
7
Comments (Contd.)Testing blocks of code
getRadioSelected:function (radioGroup)
{ /**/var selected = "";for (var i=0; i< radioGroup.length; i++) {
if (radioGroup[i].checked) {selected = radioGroup[i].value;
}}
return selected;
/**/
/** /
for (var i = 0,j=radioGroup.length; i < j; i++)
{ if (radioGroup[i].checked) { return radioGroup[i].value }
}return '';/**/
},
8
Comments (Contd.)getRadioSelected:function (radioGroup)
{ /** /var selected = "";for (var i=0; i< radioGroup.length; i++) {
if (radioGroup[i].checked) {selected = radioGroup[i].value;
}}
return selected;
/**/
/** /
for (var i = 0,j=radioGroup.length; i < j; i++)
{ if (radioGroup[i].checked) { return radioGroup[i].value }
}return '';/**/
},
9
Comments (Contd.)getRadioSelected:function (radioGroup)
{ /** /var selected = "";for (var i=0; i< radioGroup.length; i++) {
if (radioGroup[i].checked) {selected = radioGroup[i].value;
}}
return selected;
/**/
/**/
for (var i = 0,j=radioGroup.length; i < j; i++)
{ if (radioGroup[i].checked) { return radioGroup[i].value }
}return '';/**/
},
10
JS Optimization
Let’s Optimize some JavaScript…
11
Shortcuts
var cow = new Object();
cow.colour = ‘white and black’;
cow.breed = ‘Jersey’;
cow.legs = 4;
is same asvar cow =
{
colour:‘white and black’,
breed:‘Jersey’,
legs:4
};
12
Shortcuts (Contd.)
var lunch = new Array();
lunch[0]=’Dosa’;
lunch[1]=’Roti’;
lunch[2]=’Rice’;
lunch[3]=’idli’;
is same asvar lunch = [
‘Dosa’,
‘Roti’,
‘Rice’,
‘idli’
];
13
Shortcuts (Contd.)var direction;if (x > 100){
direction = 1;} else {
direction = -1;}
is same as
var direction = (x > 100) ? 1 : -1;
/* Avoid nesting these! */
14
Shortcuts (Contd.)function getWidth(clientWidth)
{
var width;
if(typeof clientWidth != 'undefined')
{
width=clientWidth;
}
else
{
width=100;}
}
is same as
function getWidth(clientWidth)
{
var width = clientWidth || 100;
}
15
Common subexpression removal
Consider:
var s = “hello, world”;
for (var i = 0; i < s.length; i++) {
// ...
}
Better way to write:
var s = “hello, world”, i, n;
for (i = 0, n = s.length; i < n; i++) {
// ...
}
16
Cache Function Pointers
• Consider:function doMyDuty(collection)
{
var i, n = collection.length;
for (i = 0; i < n; i++)
doStuff(collection[i]);
}
• Here doStuff is a function outside the scope of
doMyDuty()
17
Cache Function Pointers (Contd.)
The global lookup for every iteration can be
avoided by rewriting the doMyDuty as:
function doMyDuty(collection)
{var i, n = collection.length,
fcn = doStuff;
for (i = 0; i < n; i++)
fcn(collection[i]);
}
18
Global variables are evil
• Crawling up the scope chain
• Memory de-allocation only at end
• Can be overridden
19
Global variables are evil (Contd.)
var a = 1;
(function(){
var a = 2;function b(){var a = 3;alert(a);
}
b();
})(); // 3
20
Global variables are evil (Contd.)
var a = 1;
(function(){
var a = 2;function b(){//var a = 3;alert(a);
}
b();
})(); // 3
21
Global variables are evil (Contd.)
var a = 1;
(function(){
//var a = 2;function b(){//var a = 3;alert(a);
}
b();
})(); // 3
22
Avoid try/catch inside loops
The try/catch block creates a local scope and creates the exception object at runtime that lives in that scope.
Consider:
for ( // ... ) {
try
{ // ...
} catch (e) { // ...}
}
23
Avoid try/catch inside loops (Contd.)
Right way:
try {
for ( // ... ) {
// ...
}
} catch (e) {// ...}
24
JavaScript & DOM
DOM
• Different browsers perform differently
• Browserscope- It is a community-driven project for profiling web browsers.
http://www.browserscope.org/
25
JavaScript & DOM
• The bottleneck tends to be the DOM interface.
• There is a significant cost every time you touch the DOM tree.
• document.getElementsByTagName('*').length in firebug console gives number of DOM elements in that page. Reduce the number of DOM elements as much as possible.
26
JavaScript & DOM
• Each touch can result in a reflow computation, which is expensive.
• It is faster to manipulate new nodes before they are attached to the tree.
• Touching unattached nodes avoids the reflow cost.
• Setting innerHTML does an enormous amount of work, but browsers are really good at it, and it only touches the DOM once.
http://www.quirksmode.org/dom/innerhtml.html
27
JavaScript & DOMReplace HTML faster than innerHTML
function replaceHtml(el, html) {
var oldEl = typeof el === "string" ? document.getElementById(el) : el;
/*@cc_on // Pure innerHTML is slightly faster in IE
oldEl.innerHTML = html;
return oldEl;
@*/
var newEl = oldEl.cloneNode(false);
newEl.innerHTML = html;
oldEl.parentNode.replaceChild(newEl, oldEl);
/* Since we just removed the old element from the DOM, return a reference
to the new element, which can be used to restore variable references. */
return newEl;
};
Eg. http://dev.paypal.com/~pkhan/examples/replaceHTML.html
Source : http://blog.stevenlevithan.com/archives/faster-than-innerhtml
28
Updating DOM
Before:-
function foo() {
for (var count = 0; count < 1000; count++) {
document.body.innerHTML += 1;
}
}
29
Updating DOM (Contd).
After:-
function foo() {
var inner = '';
for (var count = 0; count < 1000; count++) {
inner += 1;
}
document.body.innerHTML += inner;
} //1000 times faster
Demo :-http://dev.paypal.com/~pkhan/examples/Domtouch.html
30
Cache Property access
Before:-For example, when making several changes to styles for an element:
this.iframe.style.top = xy[1] +"px";
this.iframe.style.left = xy[0] +"px";
this.iframe.style.height = elem.clientHeight +"px";
this.iframe.style.width = elem.clientWidth +"px";
this.iframe.style.visibility = "visible";
this.iframe.style.zIndex = "1";
31
Cache property access (Contd.)
After:-var d = this.iframe;
e = d.style; //Cache style
e.top = xy[1] +"px";
e.left = xy[0] +"px";
e.height = elem.clientHeight +"px";
e.width = elem.clientWidth +"px";
e.visibility = "visible";
e.zIndex = "1";
32
Cache property access (Contd.)
This is better than the other 2var d = this.iframe,
style;
style='top:'+xy[1] +'px;left:'+xy[0] +'px;height:'+ elem.clientHeight +'px;width:'+elem.clientHeight +'px;visibility:visible;z-Index:1;';
if (typeof d.style.cssText != “undefined”) {
d.style.cssText = style;
} else {
d.setAttribute(“style”, style);
}
33
Cache property access (Contd.)
BEST
YUD.addClass(this.iframe,"myClass");
Need not change the JavaScript code to change the Look & Feel
34
Minimize Dom Access
Before if (YUD.get("RetireSMEI") && YUD.get("RetireSMEI").checked)
showEbayForm = 1;
Better: var RetireSMEI=YUD.get("RetireSMEI");
if (RetireSMEI && RetireSMEI.checked) showEbayForm = 1;
35
Call back functions
• Consider:
var callback = new Function(“// ... “);
foo(somedata, callback);
• Better way to write:
var callback = function () { // ... };
foo(somedata, callback);
• Still better (if you don't need to reuse) callback():
foo(somedata, function () { /// ... });
36
Call back functions
A common way for one to stumble upon increasing memory problems is to pass an object's method as a callback:
YUE.addListener("recent_select", 'change', this.pushEmail); // sets wrong |this|!
Use:
YUE.addListener("recent_select", 'change', function(e){PAYPAL.love.p2p.pushEmail(e)});
Ref: https://developer.mozilla.org/en/DOM/element.addEventListener
37
Some YUI tips
1.) YUI addClass, addListener accepts array of input elements
Before
YUE.addListener("expandImg", "click", PAYPAL.Beloved.Salsa.toggleBalanceDetails); YUE.addListener("collapseImg", "click", PAYPAL.Beloved.Salsa.toggleBalanceDetails); YUE.addListener("expandLink", "click", PAYPAL.Beloved.Salsa.toggleBalanceDetails); YUE.addListener("collapseLink", "click", PAYPAL.Beloved.Salsa.toggleBalanceDetails);
Better
YUE.addListener([("expandImg", "collapseImg", "expandLink", "collapseLink“],click,function(e){("collapseLink", PAYPAL.Beloved.Salsa.toggleBalanceDetails(e));
38
Some YUI tips
2.) getElementsByClassName(className, tagName, rootNode):
Returns an array of elements that have the class name applied. Can be optionally scoped by tagName and/or a root node to increase performance.
Before:-
var pu=YUD.getElementsByClassName('pu');
Better :-
var pu=YUD.getElementsByClassName('pu','input','purchasetabs');
Even better:-
var pu=YUD.get("purchasetabs").getElementsByTagName("input");
39
Some YUI tips
3.) Use replaceClass instead of removing and adding class
Before:-
YUD.removeClass('goToMyAccount', 'hide');// The above will take more time for removal (through profiling)YUD.addClass('goToMyAccount', 'show');
Better:
YUD.replaceClass('goToMyAccount', 'hide‘,’show’);
//Does a regex replace
40
Smart Listeners Use Event Delegation when
you have to dump content via Ajax and attach events to the content elements or when u have many similar events that need to be triggered within child elements, or if you have a DHTML heavy page with node created and deleted all the time, this technique is really useful
Event Capture ->Events propagates from Top to Bottom a.k.a. Ancestor to Child
target.addEventListener(type, listener, useCapture); //false by default https://developer.mozilla.org/En/DOM:element.addEventListener
Event Capturing doesn't work in IE. And so in YUI. Because attachevent doesn’t have the third parameter
bSuccess = object.attachEvent(sEvent, fpNotify)
Eg. http://dev.paypal.com/~pkhan/examples/EventCapture.html
41
Smart Listeners
Event Bubbling -> Events propagates from Bottom to Top a.k.a. Child to Ancestor
Before :-
var lis=getElementsByTagName("li");
YAHOO.util.Event.addListener(lis, "mouseover", callback);
Event Delegation:-
YAHOO.util.Event.addListener("container", "mouseover", callback);
Example: http://dev.paypal.com/~pkhan/examples/EventBubbling.html
http://dev.paypal.com/~pkhan/examples/UsageBubbling.html
Live example:-
https://cms.paypal.com/us/cgi-bin/marketingweb?cmd=_render-content&content_ID=marketing_us/send_money
42
AJAX
•Ajax ? asynchronous doesn’t mean fast
•Use GET over post
•Use json -> small, native
•Use gzip
43
Summary• Common subexpression removal
• Use shortcuts wherever possible
• Cache Function Pointers
• Avoid globals
• Avoid try/catch inside loops
• Replace HTML faster than innerHTML
• Minimize DOM objects
• Reduce, Cache DOM Access
• Incorrect this in callback functions
• Add same listener to multiple elements
• Optimize getElementsByClassName
• Use YUI replace Class
• Use event delegation wherever appropriate
• Use GET, Gzip, JSON for Ajax
44
Questions /Suggestions ???Mail to [email protected]