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)
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
- == 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.
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.
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: JavaScript