What I don't like in JavaScript
Published on
I actually love JavaScript and despite of its flaws I think it's still has done a lot of things right and it's one of the best programming languages in the world despite of the criticism.
Here are some of the best virtues of JavaScript:
- The language has relatively simple design and this makes it easy to learn.
- It has a unified variable model. You can save anything in a variable, be it a string, number, function/class or even a ES6+ module.
- It supports closures out of the box, which makes it very easy to create functions with complex behaviors and even encapsulate things.
- It has asynchronous design and the relatively recent support for
async
/await
makes writing asynchronous code even more fun. - It has very ergonomic syntax, probably only rivaled by Python (IMHO).
- Most of the built-in functionality of the JavaScript can be patched (monkey patching). This, alongside many of the previously mentioned features, makes JavaScript an extremely flexible language.
- JavaScript is quite performant for a dynamically typed language. In some cases a properly written JavaScript code can even be as performant as an analogous C/C++ code.
- JavaScript is very versatile beyond low-level programming.
- JavaScript is backward compatible.
So what I don't like in JavaScript?
That being said, JavaScript has its fair share of design flaws, quirks and inconsistencies which can be very annoying and most of them are here to stay. Many people think the main problems are from type coercion (or casting). I think this is not the biggest problem because you can easily avoid them most of the time with the right habits. IMHO many of this type coercion quirks are kind of artificial and, especially with the right habits, it's unlikely to accidentally use them. But it's definitely a footgun too. Anyways, let's start the list of things that I don't like in JavaScript.
Typeof & Instanceof
typeof
and instanceof
are 2 related operators which are, sadly,
confused and this is a source for many mistakes and bugs.
typeof val
returns the fundamental type of the val
as a string. Here are the values
that typeof val
can return:
"undefined"
"boolean"
"number"
"string"
"bigint"
"symbol"
"object"
"function"
val instanceof ClassName
returns boolean indicating that val
is an instance or a
derivative of class ClassName
.
Typeof null
JavaScript has 9 fundamental value types, which are:
Null
Undefined
Boolean
Number
String
Bigint
Symbol
Object
Function
The problem is that for null
typeof null
will return "object"
instead of
"null"
. This is because in the first implementation of JavaScript null
was treated
like a reference to an object, and, unfortunately, this cannot be fixed now because this will break the
backward compatibility and a lot of code that depends on this quirk.
Comparing 2 NaN numbers
Another annoying thing, although not only specific to JavaScript, is comparing 2 NaN
values. In
JavaScript you can compare 2 primitive values
with the ===
or ==
operator, with the only exception of NaN
. Yes,
NaN === NaN
will yield false
. This is quite an
inconvenience, this means that NaN
should be handled in a special way. The only working ways
for comparing 2 NaN
values are:
Object.is(NaN, NaN)
isNaN(NaN)
/Number.isNaN(NaN)
-
Taking the advantage of the fact that
NaN
is the only value that isn't equal to itself:function isItNaN(value) { return value !== value; } console.log(isItNaN(NaN)); // true console.log(isItNaN(5)); // false
Actually, this one is not so much JavaScript's fault. It's because of the IEEE standard for floating point numbers, and other languages have a similar problem.
The default comparator of Array.prototype.sort()
By default Array.prototype.sort()
sorts lexicographically instead of simply comparing the elements.
This means that if you sort numbers without providing a custom callback you might think that the sorting worked
correctly:
console.log([4, 2, 6, 1, 7, 9, 5, 8, 3].sort()); // [1, 2, 3, 4, 5, 6, 7, 8, 9], seems correct
console.log([4, 2, 10, 6, 1, 7, 9, 5, 8, 3].sort()); // [1, 10, 2, 3, 4, 5, 6, 7, 8, 9], what?
So instead you mast pass a callback:
console.log([4, 2, 10, 6, 1, 7, 9, 5, 8, 3].sort((a, b) => a - b)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Sensitivity to new lines
JavaScript is designed to work even if you don't write semicolons (;
) after the statements. This
works
most of the time
pretty well. However, this causes some unexpected side effects which might be very hard to debug if you are not
familiar with this feature. For example, look at this function:
function sum(a, b) {
return
a + b;
}
console.log(sum(10, 20)); // undefined
The function will return undefined
instead of 30
. The reason is because new lines are
treated as statement separator tokens like semicolons are and the last statement will be return
.
So in order to make the function return the correct value we must be careful with the formatting:
function sum(a, b) {
return a + b;
}
console.log(sum(10, 20)); // 30
Accidentally declaring a global variable
In JavaScript if you forget to to add var
, let
or const
in the variable
declaration, the variable might be created globally without causing any exception:
function f() {
someVar = 5; // no exception
}
f();
console.log(someVar); // 5
Fortunately, this doesn't happen in strict mode and since the code in classes or modules is in strict mode, this mistake will cause an exception.
Octal numbers
If a number contains the digits 0-7 and has a leading 0, it is interpreted in base eight and not base ten:
console.log(0708); // 708
console.log(0707); // 455, 707 in octal is 455 in decimal
Array constructor
The array constructor has very different behaviors depending on the number of arguments:
- An array constructor with no arguments creates an empty array.
- An array constructor with more than one argument creates an array filled with the arguments.
- However, an array constructor with only one argument creates an array of a size equaling to the value of the argument.
console.log(new Array()); // []
console.log(new Array(3)); // [undefined, undefined, undefined]
console.log(new Array(3, 4 ,5)); // [3, 4, 5]