Javascript 字符串插值与字符串连接给出不同的结果

Kip*_*Kip 6 javascript interpolation concatenation ecmascript-6

我遇到过一种情况,Javascript 字符串插值没有给出与字符串连接相同的结果。

这是显示差异的代码的简化版本:

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"
Run Code Online (Sandbox Code Playgroud)

所以我的第一反应是,这是由于.toString()和的差异造成.valueOf()的,所以我做了一个小测试对象来验证:

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"
Run Code Online (Sandbox Code Playgroud)

但是,当我使用 Date 对象(它也有与.toString()vs不同的结果.valueOf())尝试此操作时,我没有得到相同的行为 - 这次插值和连接都使用该.toString()值:

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)"
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:连接与插值时插值如何转换为字符串的实际规则是什么,以及为什么 Date 似乎与其他对象不同?(我试图查找这一点,但到目前为止我的谷歌搜索尚未成功......)

JSFiddle 示例

tri*_*cot 5

行为的差异确实与操作符有关+,操作符背后有一个特定的过程:

关于抽象操作ToPrimitive的 ECMAScript 规范指定,如果未提供类型提示(如操作符的情况+则会发生以下情况:

  • 如果对象有一个Symbol.toPrimitive方法,那么它将被调用(带有提示“默认”)。此方法可以将呼叫转发到toString. 物体就是这种情况Date
  • 如果对象没有这样的方法,则默认调用“number” valueOf。对象就是这种情况moment

处理运算符的过程如此复杂的原因+是它也用于算术加法。

在评估模板文字时不存在这种复杂性,其中始终需要字符串连接,因此Symbol.toPrimitive将使用“字符串”提示(而不是“默认”)调用该方法,或者如果该方法不存在,toString则将调用该方法。

所以你的假设+是纯字符串连接,并不那么准确。看看使用该方法时有何不同.concat

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