
From time to time, we all make mistakes while programming in JavaScript. It may happen due to many reasons: lack of experience, bad mood, deadline, a headake and so on. Even though many issues seem small, they can still become a reason for a huge failure and hours of debugging in the future. That's why you need to pay attention to details when you're coding, and I'm going to give you some advice how to make this process easier.
A classic mistake with "for ... in"
First of all, I want to show you one of those small mistakes that many of programmers do:
var i; for (i in products) { // do something useful }
Seems absolutely simple and safe, right? No! Let me explain the problem:
1) in our example `products` can be an instance of the Array or Object data types
2) standard properties of arrays and objects aren't iterable in the for .. in loop. By standard properties I mean such properties of Array as `length` and so on.
I propose to add more details to our previous example to make everything a little bit more precise and clear:
var products = [ {title: 'Symfony2 cup', description: '+10 points to mood'}, {title: 'Symfony2 T-Shirt', description: '+100 points to programming skills'}, ], i; for (i in products) { console.log('property: ' + i); console.log('product:', products[i]); // do something useful }
Everything is fine, but what will happen if somebody overrides the Array data type? Yes, it's a bad practice, but you can't restrict it. Some people extend base data types for backward compatibility with old versions of Internet Explorer (the indexOf method). For example, somebody has added a shortcut:
Array.prototype.max = function () { return Math.max.apply(null, this); };
The problem is when you add custom properties, they become iterable. So, if you run the code snippet with products after you extend the base data type, you will see a different picture:
property: 0 product: Object {title: "Symfony2 cup", description: "+10 points to mood"} property: 1 product: Object {title: "Symfony2 T-Shirt", description: "+100 points to programming skills"} property: max product: () { return Math.max.apply(null, this); }
It means that even if the products' length property equals 2, your for .. in loop iterates through the max property, too. This will break your code, because instead of product you will try to render the max function. The devil is in the detail.
Now I want to show a few approaches how you can solve this particular issue. Then we will move to more interesting things.
Solution for arrays
The simpliest thing you can do with arrays is to use the `length` property in the simple for loop:
var i; for (i = 0; i < products.length; i += 1) { /* do your work */ }
This way you will be protected from the use-case described above. But what to do with associative arrays?
hasOwnProperty
When you work with objects instead of arrays you can't use the `length` property. Probably, you just like to use the `for .. in` loop. Using the `hasOwnProperty` method (works both for arrays and objects) you can protect your code from external changes:
var i; for (i in products) { if (products.hasOwnProperty(i)) { console.log(i); // do something useful } }
jQuery $.each
If you use jQuery, then you can rely on its user friendly `$.each` method. It handles everything under the hood, so you don't need to worry:
$.each(products, function (index, product) { console.log('index', index, 'product', product); // process data });
angular.forEach
Many libraries/frameworks have similar to $.each functions and AngularJs is one of them. AngularJs proposes its safe method forEach:
angular.forEach(products, function(value, key) { // process data again });
I've described only one potential caveat, and hope this example will motivate you to write code more carefully. Now I want to introduce a few tools that may help you in this journey:
JSHint
JSHint is a program that flags suspicious usage in programs written in JavaScript. The core project consists of a library itself as well as a CLI program distributed as a Node module
You can play with JSHint on its official site. Just write your JavaScript code in the terminal and you will see all warnings at the right side. I also recommend to enable the `strict` mode:
'use strict'; // then write your own code
It will replace some silent errors and throw Error instead. You can use JSHint in a few ways:
1) as a CLI tool. You just need to install it via npm and it will be available as jshint in the terminal.
2) You can use it as a plugin for your favourite IDE. JSHint has plugins for a lot of text editors/IDE, such as VIM, Sublime, WebStorm and so on. More over, JetBrains IDE family supports realtime code inspection with both JSHint and JSLint out of the box.
If we copy&paste our code sample to the JSHint:
var products = [], i; for (i in products) { console.log('property: ' + i); }
It will tell us:
One warning 14 The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
Great! Using JSHint as a plugin for your favourite IDE (or as a standalone tool) you will instantly see such errors and won't let them destroy your project in the future.
JSLint
The purpose of this tool is the same as JSHint has. Actually JSLint is the parent of JSHint, the difference between the two is in flexibility and strictness. JSLint is less flexible (some of its rules can't be turned off) and more strict. For example:
var a = [1, 2, 3]; for (var i = 0; i < a.length; a++) { console.log(a[i]); }
This code will pass all JSHint checks, but will fail if you check it with JSLint. The reason is in the `var` block. By the JSLint rules you must define the `var` block before anything else.
Sometimes it's unclear why your code fails in JSLint or JSHint. You can read the error, but it may be difficult to understand the nature of an error and potential problems. In such cases try to find your error at the jslinterrors site.
Conclusion
By using JSHint or JSLint you can save your project from troubles. More over, you can be sure that you and your team members follow code conventions. I recommend to start from JSHint, play with it, configure it if needed, and only then try JSLint. For many people JSHint is the best choice.