How to Avoid Common Pitfalls in JavaScript
JavaScript is a powerful language, but there are certain syntactical and behavioral pitfalls in the language that a newcomer may fall for. These pitfalls generally arise due to properties such as type coercion and evaluation methodology of ==
& ===
operator. The ==
and ===
operators evaluate an expression using the abstract equality comparison algorithms and the strict equality comparison algorithm (more about it in this tutorial: == vs === JavaScript: Double Equals and Coercion
. JavaScript is a weakly typed language. This means that variables can automatically be changed from one type to another while evaluating an expression. Although this is a very powerful feature of the language, it might give rise to some unconventional situations.
Understanding why these behaviors occur
1. Equality of empty arrays
> [] == []
false
On first look, it sounds ridiculous. An empty array is not equal to itself? But this is not what the above statement actually means. Arrays are stored by references in JavaScript and in JavaScript, the double equal operator returns true
only when you're comparing the same instances of the same type. The comparison above actually asks, "Is an instance of empty array equal to an instance of another empty array?" which is definitely false
. The statement above is similar to:
> var a = [];
undefined
> var b = [];
undefined
> a == b
false
a
and b
are references to two different locations in memory, hence the result is false. However, if both the array instances have been same
(like the one below), then the answer below would have been true
.
> var a = b = [];
undefined
> a == b
true
NOT
empty array
2. Equality of an empty array and > [] == ![]
true
Before understanding what is happening above, we need to understand the concept of truthy and falsy in JavaScript and how the !
(logical NOT) operator works. Values such as false
, null
, undefined
, NaN
, 0
, ''
and ""
are considered as falsy. Other values like true
, {}
, []
, "foo"
etc. are considered truthy. The !
operator, on the other hand, is defined for boolean values only. Any other data type will automatically be coerced into it's corresponding boolean value when operated with !
operator.
Here, ![]
evaluates to false
and the comparison actually becomes:
> [] == false
true
Isn't that supposed to be false, since empty arrays are truthy? That's right but the double equal operator evaluates expressions on certain rules. We're trying to compare an object with a boolean value and JavaScript will implicitly convert the operands to Number type. Number([])
is 0
and Number(false)
is also 0
, which evaluates to true
since zero is equal to zero.
3. Empty array plus empty array
> [] + []
""
It might look like the sum of two arrays will concatenate them and hence, on adding two empty arrays, one might get another empty array. But that's not the case in JavaScript. As stated in the ECMA language specification, the addition operator either performs string concatenation or numeric addition. Hence, the +
is not defined for arrays and JavaScript will implicitly convert arrays into their string equivalent and concatenate them. The above expression will become similar to:
> [].toString() + [].toString()
""
Concatenate of two empty strings yields another empty string and hence the above statement is valid.
4. Empty array minus empty array
> [] - []
0
This case is similar to the previous one. The -
operator is not defined for arrays or strings. Hence, JavaScript will implicitly convert the arrays into their corresponding number type. Coercion of empty array into number type yields 0
. The above expression is the same as:
> Number([]) - Number([])
0
Zero minus zero is obviously zero, which makes sense.
5. Weird array equal weird string
> [null, undefined, []] == ",,"
true
This is quite weird. When we try to compare a non-empty array with a string, Javascript coerces each array element into a string and then joins them by commas. Stringification of null
, undefined
& []
gives an empty string. Hence, the expression [null, undefined, []].toString()
yields ",,"
.
6. Plus empty array
> + []
0
That's actually not WAT. In Javascript, the unary plus operator is used to explicitly convert the object into a number type. So the above statement is similar to:
> Number([])
0
7. Empty array plus empty object
> [] + {}
"[object Object]"
You might have guessed this one. As seen in on the third item, JavaScript will coerce the array and the object into string and then concatenate them. The default conversion for an object type to string is [object type]
, where type
is the type of object. There are different types of objects in JavaScript like Object
, Function
, Array
, RegExp
etc. In this case it's Object
.
> "" + "[object Object]"
"[object Object]"
8. Empty object plus empty array
> {} + []
0
Wait! Didn't we just say that JavaScript coerces object and array to string on plus operator? This is actually a bizarre edge case of JavaScript. The JavaScript compiler understands this statement a bit differently. It considers {}
as an empty code block and + []
as another statement. We saw + []
equates to 0
which is why the above expression evaluates to 0
.
9. Empty object plus empty object
> {} + {}
NaN
This one is similar to the example above. Explicit conversion of an empty object into number type yields NaN
.
10. The null and zero
> 0 < null
false
> 0 >= null
true
> 0 == null
false
Well, that's frustrating. JavaScript double equal operator and the relational operator are based on the abstract equality comparison algorithm and the abstract relational comparison algorithm. It's just a fancy set of rules to evaluate an expression.
When we compare 0
and null
using relational operators, JavaScript coerces null
into its primitive number type. When using relational operators the comparison is similar to
> 0 < Number(null)
false
> 0 >= Number(null)
true
null
when converted to number type yields 0
which explains evaluation of the above statements. However, with the ==
operator, such a comparison doesn't fall into any category of rules defined for evaluation of expression by ==
operator. Neither 0
nor null
gets coerced and by rule, if such a case occurs, where the comparison doesn't fall into any category, false
is returned.
11. Math.min is greater than Math.max
> Math.min() > Math.max()
true
A lot of programming languages define global maximum and minimum values and so does JavaScript, but not with Math.max()
or Math.min()
. They are, instead, used to return maximum and minimum values within a group of numbers.
When these functions are called without arguments, Math.max()
returns Infinity
and Math.max()
returns -Infinity
.
> Math.min(1, 2, 3, 4)
1
> Math.max(1, 2, 3, 4)
4
JavaScript employes Number.MAX_VALUE
and Number.MIN_VALUE
for this task and they work as expected.
> Number.MIN_VALUE < Number.MAX_VALUE
true
Wrapping up
That's it! If you have anything to add feel free to comment below. You can also check out this handa JavaScript Cheat Sheet to know more about JS.
{} + {} - in chrome devtool returns “[object Object][object Object]”
That’s due to the new upgradation in V8 engine, if you try out it earlier versions it’ll return
NaN
as the interpreter earlier considered the first{ }
as an empty block and coerces the other into it’s corresponding number type (due to presence to unary plus operator). I’ll update this soon, thanks for pointing out.These are more javascript quirks than common pitfalls. Interesting nonethless…
great