Quirks of the Number Type and toString in Javascript?
The other day I came across yet another case that looks strange at first glance — something that at first even appears to be a bug in Javascript. It all started when I was faced with the need to encode an integer into a new base-36 string. The method that can be used is the Number.prototype.toString(radix) function, which is available on all objects of type Number in Javascript.
Then, keeping in mind Javascript’s coercion behavior, we can be certain that primitive data types will be wrapped as objects of their corresponding type when needed. I therefore expected that doing 22.toString(36) would be wrapped as (new Number(22)).toString(36), producing the string m. In reality, what happened?
22.toString(36);
> Uncaught SyntaxError: Invalid or unexpected token
Hmm. Strange. Okay, let’s try wrapping the number in parentheses.
(22).toString(36);
> "m"
Yes, that works as expected. But do you know what other ways make it work as expected? *smirk*
22.0.toString(36); // hmm okay fair enough
> "m"
22..toString(36); // uuumm...
> "m"
22["toString"](36); // what the...
> "m"
22 .toString(); // woi!
> "m"
Strange? Yes, at first glance. A bug? Not necessarily. So why does this happen?
Let’s Discuss Why
It turns out the problem lies with the . notation and how the Javascript parser interprets it. The . notation on a number can mean either the decimal separator or object property access on a Number via “auto-boxing”. In the case of 22.toString(36), the Javascript parser always assumes that the part to the right of the dot notation in an expression like that is the decimal part, so an error occurs when the expression is evaluated because the parser expects the right side of the dot to be a number, not a letter.
What about the cases that work above? Let’s examine them one by one.
22.0.toString(36);
> "m"
This expression works as expected because the second dot notation (.) is unambiguous to the parser, since the first dot notation is already serving as the decimal separator. The second dot must therefore indicate a property access command.
22..toString(36);
> "m"
This is actually the same as the previous case, because Javascript treats 22. as equivalent to 22.0. Even though they are different expressions, their values are the same.
22["toString"](36);
> "m"
As a quick refresher, in Javascript we can call object properties using bracket notation, written as obj["key"], which is equivalent to obj.key. In this case, the parser sees the bracket notation ["toString"] after 22, so there is no ambiguity about the intent of the expression: it is calling the toString property of 22, which is auto-boxed to (new Number(22)). That property returns a function, so (36) at the end becomes the parameter for calling that function. Not elegant, but it works.
22 .toString(36);
> "m"
This is the most “hacky” method and is not recommended because it is very unreadable and easily misunderstood by anyone reading it. Note the space between 22 and . — that is what allows this expression to run without an error.
Two key points here are:
- Javascript does not care about whitespace.
- Javascript does not allow whitespace before the decimal part of a Number.
Because of these two rules, the whitespace before the . notation is ignored and does not cause a syntax error, and the . notation is not considered ambiguous because the parser can rule out the possibility that it represents a decimal separator.
This phenomenon can generally be attributed to two characteristics of Javascript. Weak typing means there is no distinction between an integer and a float data type, so the parser has to guess the meaning of a . notation that follows a number. Primitive type coercion causes values of primitive data types to be instantly converted to objects of their corresponding type when needed — for example, when accessing properties on the parent object directly from the primitive data type.
It turns out that when these two Javascript characteristics meet, ambiguities like this can arise. This can serve as a reminder for Javascript developers to continually deepen their understanding of and attention to the core mechanisms of Javascript when writing Javascript code.
That is all I can share for now — I hope it is useful!
Sources: