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...)
CodePudding user response:
The difference in behaviour is really related to the
operator, which has a specific procedure behind it:
The ECMAScript specification on 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 totoString
. This is the case withDate
objects. - If the object has no such method, "number" is the default and
valueOf
will be called. This is the case with themoment
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 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>