Google

Jul 27, 2012

JavaScript common mistakes and tools

If you are writing Java Script code, it is worth using code quality tools like JSLint and JSHint  to avoid any pitfalls like

  • using global variables
  • leaving trailing commas in object declarations
  • not understanding the difference between closures and functions
  • forgetting to declare a var
  • naming a variable with the same name as an HTML id, etc.

It is also essential to use JavaScript testing frameworks like Jasmine, Selenium + WebDriver, QUnit, and TestSwarm. QUnit is an easy-to-use, JavaScript test suite that was developed by the jQuery project to test its code and plugins, but is capable of testing any generic JavaScript code. One of the challenges of JavaScript rich application is testing it for cross  browser compatibility. The primary goal of TestSwarm is to simplify the complicated, and time-consuming process of running JavaScript test suites in multiple browsers. It provides all the tools necessary for creating a continuous integration work-flow for your JavaScript rich application. Debugging JavaScripts can be a painful part of web development. There are handy browser plugins, built-ins and external tools to make your life easier. Here are a few such tools.

  • Cross-browser (Firebug Lite, JS Shell, Fiddler, Blackbird Javascript Debug helper, NitobiBug, DOM Inspector (aka DOMi), Wireshark / Ethereal)
  • Firefox (JavaScript Console, Firebug, Venkman, DOM Inspector, Web Developer Extension, Tamper Data, Fasterfox, etc)
  • Internet Explorer (JavaScript Console, Microsoft Windows Script Debugger, Microsoft Script Editor, Visual Web Developer, Developer Toolbar, JScript Profiler, JavaScript Memory Leak Detector)
  • Opera (JavaScript Console, Developer Console, DOM Snapshot, etc)
  • Safari ("Debug" menu, JavaScript Console, Drosera - Webkit, etc)
  • Google Chrome (JavaScript Console and Developer Tools)

Q. What are the common JavaScript errors or bad practices that you have noticed
A.

1. Not using the var to declare your variables. If you don't use "var", your variable will become global. Your code will work with global variables, but it can create strange errors that are harder to debug and fix. It is also imperative to define proper namespaces and declare variables within the scope of that namespace.


Here is an example of global window scope and a neatly packaged "name" variable and "greet" function into an object literal. You can also note that the value of 'this' is changed to the containing object, which is no longer the global "window" object. This is quite useful as you can keep a set of variables and functions abstracted into one namespace without any potential conflicts of names.

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>


<script type="text/javascript">

    //bad - global variable and function

    window.name= "window-global-scope";   //global scope

    var greet = function(greeting) {
     console.log(greeting + " " + this.name);
   }

   //good: encapsulated variable and function
   object = {
        name: "object-scope",
        
     greet: function(greeting) {
      console.log(greeting + " " + this.name);
        }
    }


</script>

</head>
<body onload="greet('hello');object.greet('howdy')">

</body>
</html>


Note: It is a best practice to define your HTML and Javascript in separate files. The above code snippet is for illustration purpose only.

The output will be

hello window-global-scope
howdy object-scope


2. Not understanding the difference between "==" operator and "===" operator. 
  • ==  operator compare the values but it doesn’t compare the data type of operands. 
  • === operator 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.
3. Not de-referencing a variable once it has been used. Setting a variable to null once it has been used will allow the garbage collector of the js engine to reclaim that object. 

4. Not understanding the difference between innerText and innerHTML. The innerHTML gets the html code inside the element and innerText gets the text inside the element. So, if you had <p> Some text </p> the innerText will only return "Some text" without the element <p>, and innerHTML will return  <p> Some text </p>  

5.  Not understanding what the implicit scope "this" refers to. For example,


function  Account(balance) {
 this.balance = balance;
 this.getTenPercentOfbalance = function() {
      return balance * 0.10;
 };
}


var mortgageAccount = new Account(10000.00);
mortgageAccount.getTenPercentOfbalance(); // returns 1000.00


Now, if you try

var tenPercentMethod = mortgageAccount.getTenPercentOfbalance();
tenPercentMethod();  // throws an error


Why did it throw an error?

The implicit "this" points to the global Window object, and the Window object does not have the function getTenPercentOfbalance( ).

The above two lines can be written with the JavaScript head object 'window' as shown below.

  var window.tenPercentMethod = window.mortgageAccount.getTenPercentOfbalance();
  window.tenPercentMethod();  // throws an error


Important: The value of this, passed to all functions, is based on the context in which the function is called at runtime.

You can fix this by

tenPercentMethod.apply(mortgageAccount); // now it uses this == mortgageAccount


When invoking constructors with the 'new' keyword, 'this' refers to the “object that is to be” when the constructor function is invoked using the new keyword. Had we not used the new keyword above, the value of this would be the context in which Account is invoked — in this case the 'window' head object.

var mortgageAccount =  Account(10000.00); // without new key word. this is passed to the constructor as 'window'.  
console.log(mortgageAccount.balance); // undefined, the value is actually set at window.balance


'this' in nested functions

You might be wondering what happens to 'this' when it is used inside of a function that is contained inside of another function. The bad news is in ECMA 3, this loses its way and refers to the head object (window object in browsers), instead of the object within which the function is defined. The good news is that this will be fixed in ECMAScript 5.

Here is another example on this reference and scope: In JavaScript, scope is resolved during execution of functions. If you have nested functions, once you have executed a function nested within a function, JavaScript has lost your scope and is defaulting to the best thing it can get, window (i.e. global). To get your scope back, JavaScript offers you two useful functions, call and apply.


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>


<script type="text/javascript">
   object = {
        name: "object-scope",
        
     greet: function() {
      nestedGreet =  function(greeting) {
           console.log(greeting + " " + this.name);
            }
   
      
      //scope is resolved during execution of functions
            nestedGreet('hello');                 //hello window-global-scope
                                                  //loses its scope and defaults to window
            nestedGreet.call(this, 'hello');      //hello object-scope 
            nestedGreet.apply(this, ['hello']);   //hello object-scope
            
     }
   }


</script>

</head>
<body onload="object.greet()">

</body>
</html>


6. Not understanding getting the function back versus invoking the function, especially when used in callback functions. The callback functions are not invoked directly. They are iether invoked asynchronously after a certain event like button click or after a certain timeout.

function sayHello(){
    return "Hello caller";
}


Now, if you do the following, you only get the function back.

var  varFunction = sayHello;  // stores the function to the variable varFunction
setTimeout(sayHello, 1000)    // can also pass it to other functions. 
                              // This is a callback function  
                              // Will call sayHello a second later.

window.load = sayHello;       // Can attach to objects. Will call sayHello when the page loads 
                              // This is a callback function 



But if you add '( )' to it as shown below, you will be actually invoking the function.

sayHello();    //invoke the function
varFunction(); //invoke the function


So, the addition of paranthese to the right invokes the function. So, incorrectly assigning like shown below will callback the function immediately.

Wrong:

setTimeout(sayHello(), 1000); // won't wait for a second 

<input id="mybutton" onclick="sayHello();return false;" type="button" value="clickMe" /> //invokes it straight a way without waiting for onclick event.


Correct:

setTimeout(sayHello, 1000); // waits for a second
//jQuery to the rescue
$('#mybutton').click(function(){
    return  "Hello caller";
})

So, it is a best practice to favor using proven JavaScript frameworks to avoid potential pitfalls.


7. Not understanding JavaScript scopes. Javascript only has global and function scopes, and does not have block scopes as in other languages like Java. In JavaScript, functions are values that can be assigned to a variable, including arrays. In the example below, the above correct code fragment uses a powerful feature of Javascript known as first order functions. In every iteration the variable item is declared that contains the current element from the array. The function that is generated on the fly contains a reference to "item" and will therefore be part of its closure. Logically, this means that in the first function captures the value  {'id': 'fname', 'help': 'Entr your first name'}, and the second function captures the value {'id': 'lname', 'help': 'Enter your surname'}, and so on. The incorrect function is also showed to understand the difference.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

<script type="text/javascript">
   
    function showHelp(help) {
   document.getElementById('help').innerHTML = help;
 }
 
 function initializeHelpWrongly() {
   var helpText = [
       {'id': 'fname', 'help': 'Entr your first name'},
       {'id': 'lname', 'help': 'Enter your surname'} 
     ];

    
   for (var i = 0; i < helpText.length; i++) {
     var item = helpText[i];
  //Wrong: by the time this function is executed the for loop would have been completed
  //and the value of the item would be the last item in the array, which is id: lname
     document.getElementById(item.id).onfocus = function() {
      console.log(item.help);
      showHelp(item.help)  
  }
   }
 }

 function initializeHelpCorrectly() {
   var helpText = [
       {'id': 'fname', 'help': 'Entr your first name'},
       {'id': 'lname', 'help': 'Enter your surname'} 
     ];

   for (var i = 0; i < helpText.length; i++) {
     var item = helpText[i];
  //In every iteration a new function is created on the fly, which contains 
  //a reference to current item being processed in the loop
  //and will therefore be part of its closure. Logically, this means that in the 
  //first function captures id: fname, and second function captures id:lname so on.
     document.getElementById(item.id).onfocus = function(item) {
       return function() {
        console.log(item.help);
        showHelp(item.help)   
       };  
     }(item);
   }
 }
 
</script>

</head>

<!-- try substituting initializeHelpWrongly()-->
<body onload="initializeHelpCorrectly();">

<p id="help">Help text appear here</p>  
<p>fname: <input type="text" id="fname" name="fname"></p>  
<p>lname: <input type="text" id="lname" name="lname"></p> 


</body>
</html>




8. Not testing the  JavaScript code for cross browser compatibility.


9. Trying to reinvent the wheel by writing substandard functions as opposed to reusing functions from proven frameworks and libraries.


Q. What tips would you give to someone requiring to perform computation intensive task using JavaScript?
A. Computation intensive JavaScript tasks, for example, in a loop can make a browser unresponsive. Here are some tips to consider.

1. Try and optimize the loop so that it completes within say 150 ~ 200 milli seconds. Anything over this value can affect the user experience.

2. Redesign the functionality by offloading the processing to a back end server.

3. The HTML 5 supports Web Worker and it brings multithreading to JavaScript. Prior to Web Worker,  developers  were creating asynchronous processing by using techniques like setTimeout(), setInterval(), XMLHttpRequest, and event handlers. The Web Workers specification defines an API for spawning background scripts in your web application. Web Workers allow you to do things like fire up long-running scripts to handle computationally intensive tasks, but without blocking the UI or other scripts to handle user interactions. 

4. If you are not on HTML 5 yet, put a wait inside the body of the loop so as to let the browser breath. Don't use sleep(5); Instead use setTimeout(..) function, which uses the non-blocking I/O paradigm. 

for (var i = 0, len = items.length; i < len; i++){
    setTimeout(function(){
        processItem(items[i])
    }, 5)  
}

Note: The above code can be further improved with a queue, dynamic batch sizes, and eliminating the need for a for loop.

Labels:

1 Comments:

Anonymous open source developer said...

Thanks for sharing. Its good to see fresh content always.

7:34 PM, October 13, 2012  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home