先记住这个答案
对数字、布尔、null、undefined、数组这些值,模板字符串和 + 拼接的结果完全一样。区别出在对象上:模板字符串的 ${} 对插值结果做的是「转成字符串」,对象会先调用 toString;+ 做的是「默认转换」,对象会先调用 valueOf,拿到原始值后如果有一边是字符串才拼接。所以一个同时定义了 valueOf 和 toString 的对象,两种写法会输出不同的内容。另外 Symbol 不能隐式转字符串,放进 ${} 或 + 都会抛 TypeError。
${}按字符串转换,对象先调toString+按默认转换,对象先调valueOf- Symbol 不能隐式插入模板字符串
- 插值里可以嵌套模板字符串,反引号不会互相截断
同一个对象,两种拼接
用一个同时定义了 valueOf 和 toString 的对象来对比,差别最明显。
const price = {
valueOf() { return 99 },
toString() { return '九十九元' }
}
// + 按默认 hint 转换,先调 valueOf;模板字符串按 string hint,先调 toString
console.log('价格:' + price)
console.log(`价格:${price}`)
// 其它值两种写法结果一样
console.log(`${null} ${undefined} ${[1, [2, 3]]} ${{}}`)
// 嵌套:插值里可以再写模板字符串,外层不会被里层的反引号截断
const items = ['a', 'b']
console.log(`共 ${items.length} 项:${items.map((x) => `<li>${x}</li>`).join('')}`)
// 插值里的运算先算完再转字符串
console.log(`${1 + 2}${'3'}`, `${'1' + 2 + 3}`)查看输出与解释
价格:99
价格:九十九元
null undefined 1,2,3 [object Object]
共 2 项:<li>a</li><li>b</li>
33 123前两行的差别在转换提示。+ 拿到对象时会以 default 为提示转原始值,普通对象在这种提示下先试 valueOf,得到 99。模板字符串以 string 为提示,先试 toString,得到「九十九元」。hint 可以理解成「调用方希望拿到什么类型」,引擎据此决定先调哪个方法。
第三行说明普通值没有区别:null 和 undefined 变成同名字符串,数组会被扁平地用逗号连接,空对象变成 [object Object]。
最后一行要注意 ${} 里是完整的表达式。${1 + 2} 先算出数字 3 再转字符串;${'1' + 2 + 3} 从左往右算,'1' + 2 已经是字符串,结果是 '123'。
嵌套和标签模板
插值里再写模板字符串是合法的,解析器会按 ${ 和 } 配对,里层的反引号不会提前结束外层字符串。拼 HTML 列表、按条件拼类名时很常用,上面代码里 items.map 那一行就是例子。嵌套超过两层就很难读了,这时应该把里层抽成变量或函数。
另一个容易忽略的点是,模板字符串前面加一个函数名就变成了标签模板,写法是函数名后面紧跟反引号,中间没有括号。这时插值不会被转成字符串,而是原样作为参数传给这个标签函数,第一个参数是字符串片段数组。styled-components、String.raw 都是靠这个机制工作的,所以在标签模板里放对象、函数都不会触发 toString。
容易答错的地方
- 模板字符串就是
+拼接的语法糖 - 结果大多数时候一样,但转换路径不同。对象同时有
valueOf和toString时,两者会得到不同的字符串,面试官问的就是这个差别。 ${}里放对象会自动转成 JSON- 不会。普通对象会变成
[object Object],要看内容得自己写JSON.stringify(obj)。数组会调用join,所以${[1, [2, 3]]}得到的是1,2,3,嵌套结构也被抹平了。 - 普通引号字符串里也能写
${} - 只有反引号才会解析插值。单引号或双引号里的
${name}就是普通文本,原样输出,这是从别的地方复制代码时常见的 bug。
面试官还会怎么问?
Symbol 放进模板字符串会怎样?怎么安全打印?
直接抛 TypeError: Cannot convert a Symbol value to a string。要显式转换:${String(sym)} 或 ${sym.description}。String() 对 Symbol 是特殊处理过的,只有隐式转换会报错。
BigInt 在模板字符串和 + 里表现一样吗?
模板字符串里 ${10n} 得到 '10',和 '' + 10n 一样,因为有一边是字符串时 BigInt 会正常转字符串。但 10n + 1 会抛 TypeError,BigInt 和普通数字不能直接做算术运算。
模板字符串里的换行和缩进会保留吗?
会原样保留,包括源代码里的缩进空格。写多行 SQL 或 HTML 时,每行前面的缩进都会进到字符串里。不想要时可以把内容顶格写,或者拼好后用 trim 和正则去掉多余空白。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。