JavaScript is a language that’s friendlier than many other programming languages in the world. However, it’s still very easy to make mistakes when writing JavaScript code through misunderstanding or overlooking stuff that we already know. By avoiding some of the mistakes below, we can make our lives easier by preventing bugs and typos in our code that bog us down with unexpected results.
Mismatching Brackets
Statements and functions nested in each other means that there’re multiple levels of brackets in each file. Usually, apps are pretty complex, so the levels can add up. This means that mismatching brackets is easy if you use a text editor that doesn’t support syntax highlight or don’t check for mismatched brackets. This can easily be avoided with modern text editors such as Visual Studio Code, Atom, and Sublime. If we want to use simpler text editors, then use linters and code formatting tools like ESLint and Prettier to detect these issues. They also let us format code automatically and detect common style issues that may occur, like quote style inconsistencies, number of characters in a line, functions that can be shortened, etc.
Mismatching Quotes and Parentheses
JavaScript lets us use single quotes, double quotes, and backticks for strings. They are equivalent. However, we should open and close with the same character. So if we open a string with a single quote, then use a single quote to close a string. If we start with double quotes or backticks, then use those respectively to close the string. Also, special characters like quotation marks have to escape to be included in a string in some cases. If you open a string with single quotes and you also use single quotes in a string, then you have to escape that to include them in the string. This also applies to double quotes and backticks. If you use double quotes in a double-quoted string, then you have to escape it. And if you use a backtick in a template string, then you have to escape the backtick character.
In if
statements, parentheses always have to wrap around the whole condition. For example, something like
if (x > y) && (y < 10) {...}
won’t work. The correct way to write that statement is to write is
if ((x > y) && (y < 10)) {...}
if we want to check that both conditions are true.
We can easily avoid this by using a JavaScript code-aware text editor, like Visual Studio code, which will highlight these syntax errors for us so that we can fix these mistakes and make the code run.
Confusing the =, ==, and === Operators
The single equal (=) operator is for assigning the data on the right side to the variable on the left side. It should not be confused with the double equals (==) and the triple equals operator (===), which are operators for comparing values of the left and the right of the operator. In JavaScript we can use all three operators in an if
statement. However, in most cases, we don’t mean to use the single equal operator inside the condition of the if
statement. What we actually want is to use the double or triple equals operator to compare the operands on the left and right of it.
For example, we shouldn’t be writing
if (x = 1){ ... }
since we don’t want to assign 1 to x
inside the if
statement. Instead, we should be using the double or triple equals operator like the code below
if (x == 1){ ... }
or
if (x === 1){ ... }
The first example, if (x = 1){ … }
, is always true
, since x
is truthy because it’s assigned the value of 1, which is truthy. What we actually want is to make a comparison which makes use of the conditional if
statement.
Using Variables Outside of Block Level
If we use the var
keyword to declare a variable, it can be referenced in any place below the var
expression. For example, suppose we have:
for (var j = 0; j < 10; j++) {
j = j + 1;
}
console.log(j);
Then we can see that j
is 10 when we run the console.log
statement on the last line of the code. This is the reason that we have the let
and const
keywords to declare variables and constants. They’re blocked scoped so that they can’t be referenced outside the block. This means that we avoid bugs that can occur when we use var
since the variable can’t be accessed outside the block that it’s declared in. As a result, we can’t assign it to anything else accidentally, causing issues with our code. Instead of using var
like we did in the example above, we can use let
like in the following example:
for (let j = 0; j < 10; j++) {
j = j + 1;
}
console.log(j);
We should get ReferenceError: j is not defined
when we run the code above, which is a good sign since we don’t want j
to be referenced outside the for
loop. If we remove the console.log
statement, then it will run.
Treating Array-Like Iterable Objects Like Arrays
In JavaScript, we can have properties with keys that are numbers. They’re automatically converted to strings before being accessed since they’re actually strings with all numerical content. Objects like the arguments
and NodeList
objects aren’t arrays, but they have properties stored with integer keys like they’re arrays. It’s easy to mistake them for arrays. The difference between arrays and array-like objects is that array-like objects aren’t arrays, but they both have a function with the Symbol Symbol.Iterator
as the identifier. In these cases, we can convert them to arrays. For example, if we want to get the arguments passed into a regular function with the arguments
object, we write something like the following with the spread operator:
function f() {
const args = [...arguments];
console.log(args);
}
f(1, 2, 3);
If we run the code above, we get [1,2,3]
.
Confusing Non-Array Objects With Arrays
Since we can access object properties and array entries with the bracket notation, which looks the same when used for arrays and regular objects, it’s easy to confuse objects and arrays.
For example, suppose we have the following code:
const obj = {
0: 1,
1: 2,
2: 3
};
console.log(obj[0]);
We see that the console.log
statement would return 1. The code above has a regular object, but in the console.log
statement, we pass in 0 as the key with the bracket notation to get the value obj[0]
, which is 1.
If we have an array and we try to access an entry by its index like in the code below:
const arr = [1, 2, 3];
console.log(arr[0]);
we also get 1 with the console.log
statement. They both use the bracket notation to access their values, but they aren’t the same. Arrays are objects, but you can iterate through them, unlike regular objects. If you try to loop through an array with the for...of
loop or the forEach
function, or try to use the spread operator with it, the example with the obj
object will result in an error since it’s not an iterable object. We can make it iterable by adding a generator function with the Symbol Symbol.iterator
to it like in the following code:
const obj = {
0: 1,
1: 2,
2: 3,
[Symbol.iterator]: function*() {
for (let prop in this) {
yield this[prop];
}
}
};
Then when we iterate the obj
object with the for...of
loop like the code below:
for (let num of obj) {
console.log(num);
}
we get back the entries of the new obj
object that we made iterable.
The spread operator would also work. If we have the following code:
console.log([...obj]);
we get [1, 2, 3]
from the console.log
output.
Most of these tricks to avoid mistakes include features from ES6. It’s a major reason why ES6 was released. Generators and the let
and const
keywords are all part of the specification. The use of ES6, along with better text editors like Visual Studio Code, help us avoid mistakes that we would otherwise would make with older technologies. It’s a great reason to keep JavaScript code and tooling up to date. ES6 was released in 2015, so there’s no reason to stay back. For older browsers like Internet Explorer, we can use something like Babel to convert it to ES5 on the fly. Or we can use a module bundler like Webpack to convert ES6 or later code to ES5 in the build artifact so that we can write code with newer versions of JavaScript.