Saturday 15 December 2012

JavaScript Mistakes You Must Avoid

If you are new to JavaScript and you write raw JavaScript or use any framework (jQuery, Mootools, Dojo, YUI) with it, you must avoid few mistakes. Actually these are my experiences when I was learning JavaScript.

Equality Operator

You may know that in js, two operators are used for comparing values. First is == (two equal signs). This operator compare the values but it doesn’t compare the data type of operands. For example if first operand is 1 and the second is true, the result will be true.
if( 1 == true ) {
    //this code will run
}
Here are more examples.
1 == "1"        //true
"true" == true  //false
1 == true       //true
"0" == 0        //true
"" == 0         //true
" " == 0        //true
"Str" == false  //false
"Str" == true   //false
 
Some of the results are unexpected specially for those who don’t know how JavaScript evaluates == operator. Actually every operand (no matter what data type it has) is converted to Number data type before comparison.

Consider the first example (1 == “1″). The first operand is already a number so no conversion occurs. Second operator is string so it’s converted or parsed to number. Now the second operator “1″ (string) is converted to 1 (number).

In the second example (“true” == true) is false because if the string contain characters other than digits, convertion to number will return NaN which means Not a Number. If anything is compared with NaN, comparison will always return false.

You can check what value will be returned after conversion to number using the Number constructor. Following are some tests in Firebug.

Number tests

Now you maybe wondering how === operator works. Equality operator (with 3 equal signs) in JavaScript compare not only the value of operands, but also the data type. If the data type of operands is different, it will always return false. Both the data type and value of operands must be same in order to make the condition true.

4 === 4         //true
"2" === 2       //false
1 === true       //false
"true" === true //false

Not Assigning null to Reference Types

It’s common mistake that many js developers don’t assign null value to variables of reference types (object or Array) when there use is finished. Take a look at following example.

var arr = [1, 2, 3];
 
//perform some processing on arr
 
//assign null to arr when its use is finished
arr = null;
 
The benefit of assigning null to reference types is that the garbage collector of js engine automatically reclaims the memory used by that variable. Remember that this is specially important for variables whose scope is large like global variables. Local variables are automatically destructed when they are out of scope (specially js engines with Mark and Sweep garbage collector).

Reference Variable Initialization

Never try to initialize multiple reference variables (like object and array) in a single assignment statement. This can be better understood using an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arr1 = [1, 2, 3]
  , arr2 = ['a', 'b', 'c'];
 
//reset both arrays
arr1 = arr2 = [];
 
//add a single item in arr2 and arr1
arr2.push( 32 );
arr1.push( 10 );
 
//print both arrays and you will see same result
//OUTPUT: 10, 32
alert( arr1.join() );
alert( arr2.join() );
 
On line 1 and 2, two arrays are created. Then those arrays are reinitialized with blank array in a single statement on line 5. The problem with this statement is that now both arrays (arr1, arr2) are pointing to same array in the memory. So changes in one will automatically be reflected in the other.

On line 8, integer 32 is added in arr2 and on line 9, integer 10 is added in arr1. To check the output of both arrays, join method is called on each of them on lines 13 and 14. Note that both arrays contain same values.

Don’t Forget Keyword var

In JavaScript you can declare variables with keyword var. But it also allows you to use variables without any declaration. There’s a very critical difference between these two ways of using variables. Consider the following example.

function createVar() {
	var myVar = 'local';
};
 
alert( myVar ); //output: undefined
 
As you can see from the above example when a variable is declared with keyword var, it’s not accessible in the outer scope. Let’s change this example to define a variable without var in function.
 
function createVar() {
	myVar = 'local';
};
 
alert( myVar ); //output: local

Note that a variable defined without keyword var in function is also accessible from the global scope. In other words var makes the variable local. So becareful when using variables in JavaScript. Always declare your variables using keyword var before using them.

Not Using Event Delegation

Attaching event handler is simple in JavaScript. For example following code adds a click handler to anchor tag having id myLink.

document.getElementById('myLink').addEventListener( 'click', function() {
   //you code goes here...
}, false );
 
Now suppose you want to add a click handler to all the td elements in table. Are you going to write a handler for each td in table?

<table id="myTable">
   <tbody>
      <tr>
         <td>1, 1</td>
	 <td>1, 2</td>
      </tr>
 
      <tr>
         <td>2, 1</td>
	 <td>2, 2</td>
      </tr>
   </tbody>
</table>
 
Here event delegates help us. In our case we will attach a single click event handler to myTable and within that we will check to see if a td element is clicked or not. In this way we don’t have to write and attach event handlers to every td in table. Such kind of handler is known as event delegate. Here’s the code for it.

document.getElementById( 'myTable' ).addEventListener( 'click', function( e ) {
      if( e.target && e.target.nodeName == 'TD' ) {
         console.log( e.target.innerHTML );
 
         //to access id
         //console.log( e.target.id );
 
         //to access className
         //console.log( e.target.className );
      }
   }, false );

innerText VS innerHTML

New js developers are often confused between innerHTML and innerText properties. Both innerHTML and innerText are used with element objects. innerHTML gets the html code inside the element and innerText gets the text inside the element.

innerHTML working

The dark gray background in Html is actually the output of innerHTML property. Note that the Html tags (in our case <p>) are also included in the output.
Let’s take a look at innerText example.

innerText working
As you can see from the above example that innerText gets the text inside the element (without html tags).

Adding Nodes in Bulk

It’s common in JavaScript to append list of nodes to some element in DOM. For example you may want to append a list of names to ul received from server using Ajax call. One way of doing this is shown below.

window.onload = function() {
//ul element - <ul id="list"></ul>
var list = document.getElementById( 'list' );
 
var item = null;
 
//suppose this json is returned from ajax call
var ajaxResponse = [
    { 'name' : 'Haiku' },
    { 'name' : 'Linux' },
    { 'name' : 'OS X' },
    { 'name' : 'Windows' }
];
 
//add all names in ajaxReponse to documentFragment
for( var i in ajaxResponse ) {
    item = document.createElement( 'li' );
    item.appendChild( document.createTextNode( ajaxResponse[ i ].name ) );
    list.appendChild( item );
}
} //end onload
 
/*
..:: OUTPUT ::..
<ul id="list">
<li>Haiku</li>
<li>Linux</li>
<li>OS X</li>
<li>Windows</li>
</ul>
*/

The problem with this method is that each time the item is appended to list in "for in" loop, the DOM gets updated immediately. This hurts performance because DOM manipulation is costly process.

“DocumentFragment is light weight version of document and it has no visual representation on the web page.”
The same output can be achieved using DocumentFragment. DocumentFragment is light weight version of document and it has no visual representation on the web page. Following is the example to append list items using DocumentFragment.

window.onload = function() {
    //create DocumentFragment
    var documentFragment = document.createDocumentFragment();
 
    var list = document.getElementById( 'list' ); //<ul id="list"></ul>
    var item = null;
 
    //suppose this json is returned from ajax call
    var ajaxResponse = [
	{ 'name' : 'Haiku' },
	{ 'name' : 'Linux' },
	{ 'name' : 'OS X' },
	{ 'name' : 'Windows' }
    ];
 
    //add all names in ajaxReponse to documentFragment
    for( var i in ajaxResponse ) {
	item = document.createElement( 'li' );
	item.appendChild( document.createTextNode( ajaxResponse[ i ].name ) );
	documentFragment.appendChild( item );
    }
 
    //append all items in documentFragment to list
    list.appendChild( documentFragment );
}

DOM Manipulation using innerHTML

Never use arithmetic assignment operator (+=) with innerHTML to add new markup. Whenever you change innerHTML, DOM updation occurs (Browser redraws the markup). So adding new markup using += operator (specially in a loop) decreases performance.
var container = document.getElementById( 'container' );
 
for( var i = 1; i <= 10; ++i ) {
    container.innerHTML += 'Item ' + i + '<br />';
}
Always use a temporary variable to store the markup you want to assign to innerHTML as shown below.
var container = document.getElementById( 'container' )
  , str = '';
 
for( var i = 1; i <= 10; ++i ) {
    str += 'Item ' + i + '<br />';
}
 
container.innerHTML += str;

No comments :