Nicolò Andronio

Nicolò Andronio

Full-stack developer, computer scientist, engineer
Evil Genius in the spare time

The regrettable inconsistency of JavaScript operators

 

Let’s talk about sums

JavaScript stems from a language standard created in no more than two weeks. It follows that the probability of having some inconsistency within the language is quite high. Whatever your expectations are, be ready to be amazed, because the intricacy of its implementation will baffle most people. If, by then end of this article, none of your eyebrows was raised then I will bow to your utter dedication; and then I will nonchalantly walk away from you because you’d scare the hell out of me!

Let’s start with the plus operator, which works both as numeric sum and as string concatenation. Here is a table of all possible combinations of several notable primitive literals when they are used as operands of a sum.

+ 0 {} [] “” NaN undefined null
0 0 “0[object Object]” “0” “0” NaN NaN 0
{} “[object Object]0” “[object Object][object Object]” “[object Object]” “[object Object]” “[object Object]NaN” “[object Object]undefined” “[object Object]null”
[] “0” “[object Object]” “” “” “NaN” “undefined” “null”
“” “0” “[object Object]” “” “” “NaN” “undefined” “null”
NaN NaN “NaN[object Object]” “NaN” “NaN” NaN NaN NaN
undefined NaN “undefined[object Object]” “undefined” “undefined” NaN NaN NaN
null 0 “null[object Object]” “null” “null” NaN NaN 0

If the summary starts with a reassuring and trivial 0 + 0 === 0, it does not proceed any better. For example, we notice that, when combined, null and 0 yield as a result 0: fascinating! We would then be tempted to assume that null is in some ways interpreted as 0, or equivalent to it in some way. Well… let’s run some tests. Open your favourite repl and follow my example:

null vs 0
1
2
3
4
5
6
> null + 0
0
> null + 0 == 0 + null
true
> null == 0
false

Ahem… interesting. Notice that I used the weak equality comparison, because of course null is of type object, while 0 is a plain number, therefore applying a strong equality comparison (value and type) would not have worked apriori. With this few tests, we acknowledge that, when coerced to number, null has the same meaning as 0, but it does not equal 0 in a wider sense. Which is bizarre to say the least!

Furthermore, we can infer that objects, strings and array always force the runtime to coerce the operands into string before applying the sum; thus falsey literals like undefined and null are converted into their verbatim value, while numbers are cast to strings. A similar phenomenon occurs when NaN is involved in a sum with other non-stringy falsey values, namely null, undefined and 0: in that case, NaN always wins and obliterates the result into oblivion, transforming it into another NaN. Ultimately, we can draw forth a sort of strength relation that dominates all interaction between instances of certain data types in the JS runtime:

On a side-node, recall that the plus sign can be used as unary operator! In that case, it will cast its operand to a number, whenever possible. In all other cases, it will simply return NaN.

Unary plus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
> +'a'
NaN
> +undefined
NaN
> +{}
NaN
> +[]
0
> +''
0
> +NaN
NaN
> +null
0

The same holds for both the binary and unary minus!

Let’s talk about arrays

Ah! Arrays! Marvelous creatures, lovely contraptions! Arrays in javascript behave in a… unique way, especially when compared to other primitives. If you go and inspect the table above in the cell dedicate to empty arrays and empty strings, you will notice that the final outcome is an empty string. Could it be that arrays and strings are somewhat similar? Let’s check:

Arrays vs strings
1
2
3
4
5
6
7
8
9
10
> [] == ''
true
> [] === ''
false
> ['a'] == 'a'
true
> ['a', 'b'] == 'ab'
false
> ['ab'] == 'ab'
true

So… from the grotesque sequence of comparisons, we learnt that an empty array is equal to an empty string (loosely speaking, as always), which could be acceptable in some twisted way. But then, javascript strikes us hard with the fiercest ball of oddity: an array containing a string is equal to that same string! What??? Should we deduce that [x] == x always hold, for every type of x? Time for another experiment:

Arrays vs everything
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> [''] == ''
true
> ['string'] == 'string'
true
> [0] == 0
true
> [{}] == {}
false
> [NaN] == NaN
false
> [null] == null
false
> [undefined] == undefined
false
> [[]] == []
false
> [] == []
false

The first three expressions were obviously true, at least according to the wicked view of the world that our javascript runtime has. Then we stumble on a couple of false comparisons, which are all justified, since:

So far, everything seems to work fine, but with the next line we discover that life ain’t so easy! The same kind of expression does not hold for null nor undefined, even if null == null and undefined == undefined!

Lastly, the revelation! Apparently, [] != [], which might be justified since an array is a particular object and may follow the object logic described above… However, I must remind you that according to our first batch of tests, [] == ''. Thus, given that [] == '', '' == [] and of course '' == '', it must follow from the transitivity of the euqality relation that [] == [], which is untrue! Therefore we have to conclude that (weak) equality in javascript is NOT transitive.

For your personal amusement
1
2
> [[[[[[[1]]]]]]] == 1
true

Oh, one last funny thing! If you evaluate an expression in the REPL, it can change value depending on whether you enclose it in parentheses or not!

For your personal amusement
1
2
3
4
> {}+0
0
> ({}+0)
'[object Object]0'