---
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 JavaScript 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 JavaScript 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.
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.
|