Introduction
The use of JavaScript has exploded over time. Now it is practically unheard of for a website not to utilize JavaScript to some extent. As a web developer who has concentrated on back-end coding in C# and front-end look and feel via HTML and CSS, my skills in JavaScript evolved over time instead of by conscious effort. While this is not uncommon, it can allow for some bad habits to be formed. This set of best practices is my way of taking a step back and addressing JavaScript as a first-class language, with both good parts and bad parts. My concentration will be on just JavaScript, regardless of where it is run. However, you will see references in here to the browser and to Visual Studio. This is simply because that is where I live, not because either are necessary for these best practices to apply. And so, without further ado, let's jump right in and see just how far down this rabbit hole goes.
Use === to Test Equality
When testing equality, a lot of languages with syntax similar to JavaScript use the double equals operator (==). However, in JavaScript you should always use triple equals (===). The difference is in how equality is determined. A triple equals operator evaluates the two items based upon their type and value. It makes no interpretations. Let's look at a couple examples:
if(1 === '1')
if(1 == '1')
if(0 === '')
if(0 == '')
The first line would equal false because the number one does not equal the character 1. That is what we would expect. The double equals operator, on the other hand, will attempt to coerce the two values to be the same type before comparing equality. This can lead to unexpected results. In the second grouping, the result using the double equals would be true. That probably isn't what we were expecting.
Just to be clear here, the same rule applies to the inequality operator as well. Looking at our above tests, we can see that both types of inequality operators work the same way as their counterparts:
if(1 !== '1')
if(1 != '1')
if(0 !== '')
if(0 != '')
The bottom line here is that we should always use the triple equals operator (or !== for not equal) rather than the double equals. The results are far more expected and predictable. The only exception would be once you are positive you understand what is happening and you absolutely need the coercion before comparison.
Include All Necessary Semicolons
Most developers won't intentionally fail to put semicolons in the proper places. However, you need to be aware that the browser will usually put in semicolons where it thinks they are necessary. This can enable a bad habit that will come back to bite you down the road. In some instances, the compiler might assume that a semicolon is not needed, which will introduce tricky, hard-to-find bugs into your code. Avoid this by always adding the proper semicolons. A good tool to help you check your JavaScript for forgotten semicolons is JSLint.
Use JSLint
As I mentioned above, JSLint is a great tool for helping you identify common problems in your JavaScript code. You can paste your code into the website listed above, you can use a different site like JavaScriptLint.com, or you can use one of the many downloadable JSLint tools. For instance, Visual Studio has an add-in for JSLint that will allow you to check for errors at compile-time (or manually).
Whatever you choose to do, the key point here is to run a tool like JSLint on your code. It will pick up bad code that is being masked by the browser. This will make your code cleaner and it will help to prevent those pesky bugs from showing up in production.
Use a Namespace
When you first start using JavaScript, the temptation is to just declare everything and use it as needed. This places all of your functions and variables into the global scope. The problem with this, besides it being sloppy, is that it makes your code extremely vulnerable to being affected by other code. For instance, consider the following example:
var cost = 5;
console.log(cost);
Imagine your surprise when the alert pops up and says "expensive" instead of 5. When you trace it down, you might find that a different piece of JavaScript somewhere else used a variable called cost to store text about cost for a different section of your application.
The solution to this is namespacing. To create a namespace, you simply declare a variable and then attach the properties and methods you want to it. The above code would be improved to look like this:
var MyNamespace = {};
MyNamespace.cost = 5;
console.log(MyNamespace.cost);
The resulting value would be 5, as expected. Now you only have one variable directly attached to the global context. The only way you should have a problem with naming conflicts now is if another application uses the same namespace as you. This problem will be much easier to diagnose, since none of your code will work (all of your methods and properties will be wiped out).
Avoid Using Eval
The Eval function allows us to pass a string to the JavaScript compiler and have it execute as JavaScript. In simple terms, anything you pass in at runtime gets executed as if it were added at design time. Here is an example of what that might look like:
eval("alert('Hi');");
This would pop up an alert box with the message "Hi" in it. The text inside the eval could have been passed in by the user or it could have been pulled from a database or other location.
There are a couple reasons why the eval function should be avoided. First, it is significantly slower than design time code. Second, it is a security risk. When code is acquired and executed at runtime, it opens a potential threat vector for malicious programmers to exploit. Bottom line here is that this function should be avoided at all costs.
Use Decimals Cautiously
When is 0.1 + 0.2 not equal to 0.3? When you do the calculation in JavaScript. The actual value of 0.1 + 0.2 comes out to be something like 0.30000000000000004. The reason for this (nope, not a bug) is because JavaScript uses Binary Floating Point numbers. To get around this issue, you can multiply your numbers to remove the decimal portion. For instance, if you were to be adding up the cost of two items, you could multiply each price by 100 and then divide the sum by 100. Here is an example:
var hamburger = 8.20;
var fries = 2.10;
var total = hamburger + fries;
console.log(total);
hamburger = hamburger * 100;
fries = fries * 100;
total = hamburger + fries;
total = total / 100;
console.log(total);
Start Blocks on the Same Line
Most developers that write software in other C-family programming languages use the Allman style of formatting for block quotes. This places the opening curly brace on its own line. This pattern would look like this in JavaScript:
if(myState === 'testing')
{
console.log('You are in testing');
}
else
{
console.log('You are in production');
}
This will work most of the time. However, JavaScript is designed in such a way that following the K&R style of formatting for blocks is a better idea. This format starts the opening curly brace on the same line as the preceding line of code. It looks like this:
if(myState === 'testing') {
console.log('You are in testing');
} else {
console.log('You are in production');
}
While this may only seem like a stylistic difference, there can be times when there is a impact on your code if you use the Allman style. Earlier we talked about the browser inserting semicolons where it felt they were needed. One fairly serious issue with this is on return statements. Let's look at an example:
return
{
age: 15,
name: Jon
}
You would assume that the object would be returned but instead the return value will be undefined
. The reason for this is because the browser has inserted a semicolon after the word return, assuming that one is missing. While return is probably the most common place where you will experience this issue, it isn't the only place. Browsers will add semi-colons after a number of keywords, including var and throw.
It is because of this type of issue that is is considered best practice to always use the K&R style for blocks to ensure that your code always works as expected.
Use Explicit Blocks
There are a number of shortcuts and one-liners that can be used in lieu of their explicit counterparts. In most cases, these shortcuts actually encourage errors in the future. For instance, this is acceptable notation:
if (i > 3)
doSomething();
The problem with this is what could happen in the future. Say, for instance, a programmer were told to reset the value of i
once the doSomething()
function was executed. The programmer might modify the above code like so:
if (i > 3)
doSomething();
i = 0;
In this instance, i
will be reset to zero even if the if statement evaluates to false. The problem might not be apparent at first and this issue doesn't really jump off the page when you are reading over the code in a code review.
Instead of using the shortcut, take the time necessary to turn this into the full notation. Doing so will protect you in the future. The final notation would look like this:
if (i > 3) {
doSomething();
}
Now when anyone goes in to add additional logic, it becomes readily apparent where to put the code and what will happen when you do.
Declare All Variables First
Most languages that conform to the C-family style will not put an item into memory until the program execution hits the line where the item is initialized.
JavaScript is not like most other languages. It utilizes function-level scoping of variables and functions. When a variable is declared, the declaration statement gets hoisted to the top of the function. The same is true for functions. For example, this is permissible (if horrible) format:
function simpleExample(){
i = 7;
console.log(i);
var i;
}
What happens behind the scenes is that the var i;
line declaration gets hoisted to the top of the simpleExample
function. To make matters more complicated, not only the declaration of a variable gets hoisted but the entire function declaration gets hoisted. Let's look at an example to make this clearer:
function complexExample() {
i = 7;
console.log(i);
console.log(testOne());
console.log(testTwo());
var testOne = function(){ return 'Hi from test one'; }
function testTwo(){ return 'Hi from test two'; }
var i = 2;
}
Let's rewrite this function the way JavaScript sees it once it has hoisted the variable declarations and functions:
function complexExample() {
var testOne;
function testTwo(){ return 'Hi from test two'; }
var i;
i = 7;
console.log(i);
console.log(testOne());
console.log(testTwo());
testOne = function(){ return 'Hi from test one'; }
i = 2;
}
See the difference? The function testOne
didn't get hoisted because it was a variable declaration (the variable is named testOne and the declaration is the anonymous function). The variable i
gets its declaration hoisted and the initialization actually becomes an assignment down below.
In order to minimize mistakes and reduce the chances of introducing hard to find bugs in your code, always declare your variables at the top of your function and declare your functions next, before you need to use them. This reduces the chances of a misunderstanding about what is going on in your code.
Do Not Use "With"
It is possible to shorten a long namespace using the with statement. For instance, this is technically correct syntax:
with (myNamespace.parent.child.person) {
firstName = 'Jon';
lastName = 'Smyth';
}
That is equivalent of typing the following:
myNamespace.parent.child.person.firstName = 'Jon';
myNamespace.parent.child.person.lastName = 'Smyth';
The problem is that there are times when this goes badly wrong. Like many of the other common pitfalls of JavaScript, this will work fine in most circumstances. The better method of handling this issue is to assign the object to a variable and then reference the variable like so:
var p = myNamespace.parent.child.person;
p.firstName = 'Jon';
p.lastName = 'Smyth';
This method works every time, which is what we want out of a coding practice.
Be Careful Using typeof
Again, the edge cases here will bite you if you aren't careful. Normally, typeof
returns the string representation of the value type ('number', 'string', etc.) The problem comes in when evaluating NaN ('number'), null ('object'), and other odd cases. For example, here are a couple of comparisons that might be unexpected:
var i = 10;
i = i - 'taxi';
if (typeof(i) === 'number') {
console.log('i is a number');
} else {
console.log('You subtracted a bad value from i');
}
The resulting alert message would be "i is a number", even though clearly it is NaN (or "Not a Number"). If you were attempting to ensure the passed in value (here it is represented by 'taxi') subtracted from i was a valid number, you would get unexpected results.
While there are times when it is necessary to try to determine the type of a particular value, be sure to understand these (and other) peculiarities about typeof
that could lead to undesirable results.
Treat parseInt With Care
Just like the typeof
function, the parseInt
function has quirks that need to be understood before it is used. There are two major areas that lead to unexpected results. First, if the first character is a number, parseInt
will return all of the number characters it finds until it hits a non-numeric character. Here is an example:
parseInt("56");
parseInt("Joe");
parseInt("Joe56");
parseInt("56Joe");
parseInt("21.95");
Note that last example I threw in there to trip you up. The decimal point is not a valid character in an integer, so just like any other character, parseInt
stops evaluating on it. Thus, we get 21 when evaluating 21.95 and no rounding is attempted.
The second pitfall is in the interpretation of the number. It used to be that a string with a leading zero was determined to be a number in octal format. Ecmascript 5 (JavaScript is an implementation of Ecmascript) removed this functionality. Now most numbers will default to base 10 (the most common numbering format). The one exception is a string that starts with "0x". This type of string will be assumed to be a hexadecimal number (base 16) and it will be converted to a base 10 number on output. To specify a number's format, thus ensuring it is properly evaluated, you can include the optional parameter called a radix. Here are some more examples to illustrate these possibilities:
parseInt("08");
parseInt("0x12");
parseInt("12", 16);
Do Not Use Switch Fall Through
When you execute a switch
statement, each case
statement should be concluded by a break
statement like so:
switch(i) {
case 1:
console.log('One');
break;
case 2:
console.log('Two');
break;
case 3:
console.log('Three');
break;
default:
console.log('Unknown');
break;
}
If you were to assign the value of 2 to the variable i
, this switch statement would fire an alert that says "Two". The language does permit you to allow fall through by omitting the break statement(s) like so:
switch(i) {
case 1:
console.log('One');
break;
case 2:
console.log('Two');
case 3:
console.log('Three');
break;
default:
console.log('Unknown');
break;
}
Now if you passed in a value of 2, you would get two alerts, the first one saying "Two" and the second one saying "Three". This can seem to be a desirable solution in certain circumstances. The problem is that this can create false expectations. If you do not see that a break statement is missing, you may add logic that gets fired accidentally. Conversely, you may notice later that a break statement is missing and you might assume this is a bug. The bottom line is that fall through should not be used intentionally in order to keep your logic clean and clear.
Avoid For...In Loops
The For...In loop works as it is intended to work, but how it works surprises people. The basic overview is that it loops through the attached, enumeration-visible members on an object. It does not simply walk down the index list like a basic for loop does. The following two examples are NOT equivalent:
for(var i = 0; i < arr.length; i++) {}
for(var i in arr) {}
In some cases, the output will act the same in the above two cases. That does not mean they work the same way. There are three major ways that for...in is different than a standard for loop. These are:
- It loops through all of the enumeration-visible members, which means it will pick up functions or other items attached to the object or its prototype.
- The order is not predictable (especially cross-browser).
- It is slower than a standard for loop.
If you fully understand for...in and know that it is the right choice for your specific situation, it can be a good solution. However, for the other 99% of situations, you should use a standard for loop instead. It will be quicker, easier to understand, and less likely to cause weird bugs that are hard to diagnose.
Use Var to Declare Variables
When declaring a variable, always use the var keyword unless you are specifically attaching the variable to an object. Failure to do so attaches your new variable to the global scope (window if you are in a browser). Here is an example to illustrate how this works:
function carDemo() {
var carMake = 'Dodge';
carModel = 'Charger';
}
console.log(carMake);
console.log(carModel);
The declaration of the carModel variable is the equivalent of saying window.carModel = 'Charger';
. This clogs up the global scope and endangers your other JavaScript code blocks, since you might inadvertently change the value of a variable somewhere else.
Avoid Reserved / Special Words
JavaScript is rather flexible with what it allows you to do. This isn't always a good thing. For instance, when you create a function, you can specify that one of the parameters be named arguments
. This will overwrite the arguments object that every function is given by inheritance. This is an example of a special word that isn't truly reserved. Here is an example of how it would work:
function CorrectWay() {
for(var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
function WrongWay(arguments) {
for(var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
CorrectWay('hello', 'hi');
WrongWay('hello', 'hi');
There are also reserved words that will cause you issues when you attempt to run your application. A complete listing of these words can be found at the Mozilla Developer Network. While there are work-arounds to use some of these words, avoid doing so if at all possible. Instead, use key words that won't conflict with current or potential future reserved or special words.
Be Consistent
When I originally developed my list of best practices, this one was so obvious I overlooked it. Fortunately Daniele Rota Nodari pointed it out to me. Keeping a consistent standard is important to writing easily understandable code. Matching the coding style of the application you are working in should become second nature, even if that means changing your personal style for the duration of the project. When you get the opportunity to start a project, make sure that you have already established a personal coding style that you can apply in a consistent manner.
While being inconsistent with how you write your code won’t necessarily add bugs into your application, it does make your code harder to read and understand. The harder code is to read and understand, the more likely it is that someone will make a mistake. A good post on JavaScript coding styles and consistency in applying them can be found here: http://flaviocopes.com/javascript-coding-style/. The bottom line here is that you need to write consistent code. If you bring snippets into your application, format them to match your existing style. If you are working in someone else’s application, match your code to the existing style.
Read Good Code
As with any software development language, reading the code of other developers will help you improve your own skills. Find a popular open source library and peruse the code. Figure out what they are doing and then identify why they chose to do things that way. If you can't figure it out, ask someone. Push yourself to learn new ways to attach common problems.
Conclusion
JavaScript is not C#. It is not Java or Java-lite. It is its own language. If you treat it as such, you will have a much easier time navigating its particular peculiarities. As you may have noticed, the common theme throughout many of these best practices is that there are hidden pitfalls that can be avoided by simply modifying how you approach certain problems. Little formatting and layout techniques can make a big difference in the success of your project.
Before I finish, I wanted to point out that there are a number of best practices related to JavaScript in HTML that I have not mentioned. For instance, a simple one is to include your JavaScript files at the bottom of your body
tag. This omission is intentional. While I've made passing references to both the browser and Visual Studio, the above tips are purely about JavaScript. I will be writing a separate article that covers JavaScript in the browser.
Thus concludes my attempt at compiling a list of JavaScript best practices. Please be sure to let me know if you have others that you think should be added to the list.
I am currently a Senior Software Developer at a company in Illinois called DeGarmo. My primary skills are in .NET, SQL, JavaScript, and other web technologies although I have worked with PowerShell, C, and Java as well.
In my previous positions, I have worked as a lead developer, professor and IT Director. As such, I have been able to develop software on a number of different types of systems and I have learned how to correctly oversee the overall direction of technology for an organization. I've developed applications for everything from machine automation to complete ERP systems.
I enjoy taking hard subjects and making them easy to understand for people unfamiliar with the topic.