JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.
In this article, we’ll look at some confusing expressions that we shouldn’t be writing in JavaScript code.
Confusing Multiline Expressions
JavaScript has the automatic semicolon insertion(ASI) feature which adds semicolons automatically.
Therefore, we can omit the semicolon and still have a valid JavaScript code. However, this doesn’t mean that they’re easy to read for users.
In JavaScript, a newline character always ends a statement like with a semicolon except when:
- The statement has an unclose parentheses, array literal, object literal or ends in some other way that’s not a valid way to end a statement
- The lines is
--
or++
- It’s a
for
,while
,do
,if
, orelse
and there’s no(
- The next lines start with arithmetic or other binary operators that can only be found between 2 operands
There’re cases where multiline expressions where the new line looks like it’s ending a statement, but it’s not. For instance, the following aren’t are multiline but are actually one expression:
let b = 3
let a = b
(1 || 2).c();
We’ll get a ‘b is not a function’ message since the last 2 lines are interpreted as:
let a = b(1 || 2).c();
Another would be the following:
let addNumber = ()=>{}
let foo = 'bar'
[1, 2, 3].forEach(addNumber);
The code above would get us syntax errors. Therefore, we should put semicolons at the end of each statement so that no developer or JavaScript interpreter would be confused or give errors.
Unreachable Code After return, throw, continue, and break statement
Unreachable code after return
, throw
, break
, and continue
are useless because these statements unconditionally exits a block of code.
Therefore, we should never have code that’s never going to be run after those lines.
For instance, the following function has unreachable code:
const fn = () => {
let x = 1;
return x;
x = 2;
}
x = 2
is unreachable since comes after the return
statement.
Other pieces of code that we shouldn’t write include:
while(true) {
break;
console.log("done");
}
const fn = () => {
throw new Error("error");
console.log("done");
}
Therefore, we should write:
const fn = () => {
let x = 1;
return x;
}
Control Flow Statements in finally Blocks
In JavaScript, the finally
block is always run before the try...catch
block finishes when it’s added after a try...catch
block. Therefore, whenever we have return
, throw
, break
, continue
in finally
, the ones in try...catch
are overwritten.
For instance, if we have:
let x = (() => {
try {
return 'foo';
} catch (err) {
return 'bar';
} finally {
return 'baz';
}
})();
Then x
would be 'baz'
since the finally
block’s return
statement runs before the ones in the try...catch
block.
Therefore we shouldn’t add flow control statements to the finally block since it made the ones in try...catch
useless. We should instead write something like:
let x = (() => {
try {
return 'foo';
} catch (err) {
return 'bar';
} finally {
console.log('baz');
}
})();
so that the return
statements in try...catch
have a chance to run.
Negating the Left Operand of Relational Operators
Negating the left operand doesn’t always negate the whole operation expression in JavaScript.
For instance:
!prop in object
only negates prop
and:
!foo instanceof C
only negates foo
.
Therefore, !prop in object
is actually true in object
or false in object
depending on the truthiness of prop
.
Likewise, !foo instanceof C
is actually, true instanceof C
or false instanceof C
depending on the truthiness of foo
.
Therefore, we should wrap the whole expression in parentheses so it actually negates the return value of the whole expression as follows. Therefore:
!(`prop in object)`
and:
!(`foo instanceof C)`
are what we want.
This way, there’s no confusion about what we’re trying to do with those expressions.
Conclusion
There’re many ways to create confusing expressions with JavaScript. One way is to omit semicolons at the end of the line. Omitting semicolons can create ambiguous expressions easily.
We can also create useless code by writing unreachable expressions. They’re useless so they shouldn’t be written. This include writing code after return
, break
, throw
, and continue
. Also, writing those statements in finally
also make the ones in try...catch
useless.
Finally, negating the left operand of the in
and instanceof
operators only negate the left operand rather than the whole expression.