Search code examples
javascriptecmascript-6concatenationinterpolation

Javascript string interpolation gives different result than string concatenation


I ran across a case where Javascript string interpolation is not giving the same result as string concatenation.

Here is a simplified version of the code showing the difference:

const mmt = moment();
console.log('concatenated: ' + mmt); // "concatenated: 1651070909974"
console.log(`interpolated: ${mmt}`); // "interpolated: Wed Apr 27 2022 10:48:29 GMT-0400"
console.log('mmt.valueOf(): ' + mmt.valueOf()); // "mmt.valueOf(): 1651070909974"
console.log('mmt.toString(): ' + mmt.toString()); // "mmt.toString(): Wed Apr 27 2022 10:48:29 GMT-0400"

So my immediate thought was that it was due to a difference in .toString() and .valueOf(), so I made a small test object to verify:

const obj = {
  toString: () => 'toString',
  valueOf: () => 'valueOf',
};

console.log('concatenated: ' + obj); // "concatenated: valueOf"
console.log(`interpolated: ${obj}`); // "interpolated: toString"
console.log('obj.valueOf(): ' + obj.valueOf()); // "obj.valueOf(): valueOf"
console.log('obj.toString(): ' + obj.toString()); // "obj.toString(): toString"

However, when I tried this with a Date object (which also has a different result from .toString() vs .valueOf()), I do not get the same behavior--this time interpolation and concatenation both use the .toString() value:

const dte = new Date();
console.log('concatenated: ' + dte); // "concatenated: Wed Apr 27 2022 10:48:29 GMT-0400 (Eastern Daylight Time)"
console.log(`interpolated: ${dte}`); // "interpolated: Wed Apr 27 2022 10:48:29 GMT-0400 (Eastern Daylight Time)"
console.log('dte.valueOf(): ' + dte.valueOf()); // "dte.valueOf(): 1651070909974"
console.log('dte.toString(): ' + dte.toString()); // "dte.toString(): Wed Apr 27 2022 10:48:29 GMT-0400 (Eastern Daylight Time)"

So my questions is: What are the actual rules for how an interpolated value is converted to a string when concatenated vs interpolated, and why does Date seem to be different from other objects? (I have tried to look this up, but thus far my googling has been unsuccessful...)

JSFiddle Example


Solution

  • The difference in behaviour is really related to the + operator, which has a specific procedure behind it:

    The ECMAScript specification on the abstract operation ToPrimitive specifies that if no type hint is provided (as is the case with the + operator) the following happens:

    • If the object has a Symbol.toPrimitive method, then it will be called (with hint "default"). This method may forward the call to toString. This is the case with Date objects.
    • If the object has no such method, "number" is the default and valueOf will be called. This is the case with the moment object.

    The reason for this complex procedure in handling the + operator, is that it also serves for the arithmetic addition.

    This complexity is not present in evaluating template literals, where always string concatenation is intended, and so the Symbol.toPrimitive method will be called with the "string" hint (instead of "default"), or if that method does not exist, toString will be called.

    So your assumption that + is a pure string concatenation, is not that accurate. See how it is also different when you use the .concat method:

    const mmt = moment();
    console.log('concatenated: '.concat(mmt));
    // Not same result as with +
    console.log('plus operator: ' + mmt);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.3/moment.min.js"></script>