Why isn't Number.toString() being called on a string concatenation operation?

While experimenting with the default behavior of the Object.toString() function, I noticed that string concatenations like the one below predictably call toString() on the target objects:

var x = { toString: () => "one" };
var y = { toString: () => "two" };
var output = 'x is ' + x + ' while y is ' + y;
console.log(output); // writes "x is one while y is two" to the console

However, the same is not observed when toString() is overridden in the prototypes of Number and Boolean, for instance. It's necessary to "force" a toString() call in order to get the desired output:

Number.prototype.toString = () => "42";
Boolean.prototype.toString = () => "whatever you wish";

var a = 1;
console.log('The answer is ' + a); // writes "The answer is 1"
console.log('The answer is ' + a.toString()); // writes "The answer is 42"

var b = true;
console.log('Girl you know it\'s ' + b); // writes "Girl you know it's true"
console.log('Girl you know it\'s ' + b.toString()); // writes "Girl you know it's whatever you wish"

This is consistent across browsers (tested on Chrome, Firefox and Edge) so I presume it's standard behavior. Where is it documented? Is there a list of the standard objects that get special treatment during string concatenations?

Answers:

Answer

JavaScript will freely convert between number primitives and Number Objects.

If you look at the rules for the + operator you will see that it says:

7 Let lprim be ToPrimitive(lval).

and

9 Let rprim be ToPrimitive(rval).

So when dealing with + it will try to work with primitives over objects.

It then has the ToString rule to convert the primitive to a string.

Number See 7.1.12.1.

… which then describes a long special case.


In short:

It converts the value to a primitive and then has special case rules for converting numbers to strings.

This means it doesn't treat it as an Object or call the .toString method that could normally override such rules.

Answer
  1. Plus semantics

    • If Type(lprim) is String or Type(rprim) is String, then
      • Let lstr be ? ToString(lprim).
      • Let rstr be ? ToString(rprim).
      • Return the string-concatenation of lstr and rstr.
  2. ToString

    Argument type: Number => Return NumberToString(argument).

  3. NumberToString

    • If m is NaN, return the String "NaN".
    • If m is +0 or -0, return the String "0".
    • If m is less than zero, return the string-concatenation of "-" and ! ToString(-m).
    • If m is +?, return the String "Infinity".

    • ... You get the idea.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.