模板字面量(模板字符串)
模板字面量 是由反引号 (`) 字符包围的字面量,允许使用多行字符串、通过嵌入表达式实现字符串插值,以及称为标签模板的特殊构造。
模板字面量有时也被非正式地称为 模板字符串,因为它们最常用于字符串插值(通过替换占位符来创建字符串)。然而,标签模板字面量可能不会生成字符串;它可以与自定义的标签函数一起使用,以对模板字面量的不同部分执行任何所需的操作。
语法
`string text`
`string text line 1
string text line 2`
`string text ${expression} string text`
tagFunction`string text ${expression} string text`
参数
描述
模板字面量使用反引号 (`) 字符而不是双引号或单引号包围。
除了包含普通字符串,模板字面量还可以包含其他部分,称为 占位符,它们是美元符号和花括号包围的嵌入表达式:${expression}。字符串和占位符被传递给一个函数——可以是默认函数,也可以是你提供的函数。默认函数(当你没有提供自己的函数时)只执行字符串插值来替换占位符,然后将各部分连接成一个单独的字符串。
要提供你自己的函数,请在模板字面量前面加上函数名;结果称为标签模板。在这种情况下,模板字面量将传递给你的标签函数,你可以在其中对模板字面量的不同部分执行任何所需的操作。
要在模板字面量中转义反引号,请在反引号前面加上反斜杠 (\)。
`\`` === "`"; // true
美元符号也可以被转义,以防止插值。
`\${1}` === "${1}"; // true
多行字符串
源代码中插入的任何换行符都是模板字面量的一部分。
使用普通字符串,你必须使用以下语法才能获得多行字符串
console.log("string text line 1\nstring text line 2");
// "string text line 1
// string text line 2"
使用模板字面量,你可以通过以下方式实现相同效果
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"
与普通字符串字面量一样,你可以通过用反斜杠 (\) 转义换行符来将单行字符串写在多行上,以提高源代码的可读性
console.log(`string text line 1 \
string text line 2`);
// "string text line 1 string text line 2"
字符串插值
没有模板字面量时,当你想将表达式的输出与字符串结合时,你将使用连接运算符 + 连接它们
const a = 5;
const b = 10;
console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
// "Fifteen is 15 and
// not 20."
这可能难以阅读——尤其是在你有多个表达式时。
使用模板字面量,你可以避免连接运算符——并通过使用 ${expression} 形式的占位符来对嵌入表达式进行替换,从而提高代码的可读性
const a = 5;
const b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
请注意,这两种语法之间存在细微差异。模板字面量直接将其表达式强制转换为字符串,而加法运算符首先将其操作数强制转换为原始值。有关更多信息,请参阅+ 运算符的参考页面。
嵌套模板
在某些情况下,嵌套模板是实现可配置字符串最简单(也许更具可读性)的方法。在反引号分隔的模板中,通过在模板内的 ${expression} 占位符中使用内部反引号,可以轻松允许内部反引号。
例如,如果没有模板字面量,如果你想根据特定条件返回某个值,你可以这样做
let classes = "header";
classes += isLargeScreen()
? ""
: item.isCollapsed
? " icon-expander"
: " icon-collapser";
使用模板字面量但不嵌套,你可以这样做
const classes = `header ${
isLargeScreen() ? "" : item.isCollapsed ? "icon-expander" : "icon-collapser"
}`;
通过嵌套模板字面量,你可以这样做
const classes = `header ${
isLargeScreen() ? "" : `icon-${item.isCollapsed ? "expander" : "collapser"}`
}`;
标签模板
模板字面量的一种更高级形式是 标签 模板。
标签允许你使用函数解析模板字面量。标签函数的第一个参数包含一个字符串值数组。其余参数与表达式相关。
标签函数可以对这些参数执行任何所需的操作,并返回处理后的字符串。(或者,它可以返回完全不同的内容,如以下示例之一所述。)
用于标签的函数名可以是任何你想要的。
const person = "Mike";
const age = 28;
function myTag(strings, personExp, ageExp) {
const str0 = strings[0]; // "That "
const str1 = strings[1]; // " is a "
const str2 = strings[2]; // "."
const ageStr = ageExp < 100 ? "youngster" : "centenarian";
// We can even return a string built using a template literal
return `${str0}${personExp}${str1}${ageStr}${str2}`;
}
const output = myTag`That ${person} is a ${age}.`;
console.log(output);
// That Mike is a youngster.
标签不必是普通的标识符。你可以使用优先级大于 16 的任何表达式,包括属性访问、函数调用、new 表达式,甚至是另一个标签模板字面量。
console.log`Hello`; // [ 'Hello' ]
console.log.bind(1, 2)`Hello`; // 2 [ 'Hello' ]
new Function("console.log(arguments)")`Hello`; // [Arguments] { '0': [ 'Hello' ] }
function recursive(strings, ...values) {
console.log(strings, values);
return recursive;
}
recursive`Hello``World`;
// [ 'Hello' ] []
// [ 'World' ] []
虽然语法上允许,但 无标签 模板字面量是字符串,当它们被链式调用时会抛出 TypeError。
console.log(`Hello``World`); // TypeError: "Hello" is not a function
唯一的例外是可选链,它会抛出语法错误。
console.log?.`Hello`; // SyntaxError: Invalid tagged template on optional chain
console?.log`Hello`; // SyntaxError: Invalid tagged template on optional chain
请注意,这两个表达式仍然可解析。这意味着它们不受自动分号插入的影响,自动分号插入只会插入分号以修复否则无法解析的代码。
// Still a syntax error
const a = console?.log
`Hello`
标签函数甚至不需要返回字符串!
function template(strings, ...keys) {
return (...values) => {
const dict = values[values.length - 1] || {};
const result = [strings[0]];
keys.forEach((key, i) => {
const value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join("");
};
}
const t1Closure = template`${0}${1}${0}!`;
// const t1Closure = template(["","","","!"],0,1,0);
t1Closure("Y", "A"); // "YAY!"
const t2Closure = template`${0} ${"foo"}!`;
// const t2Closure = template([""," ","!"],0,"foo");
t2Closure("Hello", { foo: "World" }); // "Hello World!"
const t3Closure = template`I'm ${"name"}. I'm almost ${"age"} years old.`;
// const t3Closure = template(["I'm ", ". I'm almost ", " years old."], "name", "age");
t3Closure("foo", { name: "MDN", age: 30 }); // "I'm MDN. I'm almost 30 years old."
t3Closure({ name: "MDN", age: 30 }); // "I'm MDN. I'm almost 30 years old."
标签函数接收的第一个参数是一个字符串数组。对于任何模板字面量,其长度等于替换次数(${…} 的出现次数)加一,因此总是非空的。
对于任何特定的标签模板字面量表达式,无论字面量被评估多少次,标签函数都将始终使用完全相同的字面量数组进行调用。
const callHistory = [];
function tag(strings, ...values) {
callHistory.push(strings);
// Return a freshly made object
return {};
}
function evaluateLiteral() {
return tag`Hello, ${"world"}!`;
}
console.log(evaluateLiteral() === evaluateLiteral()); // false; each time `tag` is called, it returns a new object
console.log(callHistory[0] === callHistory[1]); // true; all evaluations of the same tagged literal would pass in the same strings array
这允许标签根据其第一个参数的身份缓存结果。为了进一步确保数组值的稳定性,第一个参数及其raw 属性都被冻结,因此你无法以任何方式修改它们。
原始字符串
标签函数第一个参数中提供的特殊 raw 属性允许你访问输入的原始字符串,而无需处理转义序列。
function tag(strings) {
console.log(strings.raw[0]);
}
tag`string text line 1 \n string text line 2`;
// Logs "string text line 1 \n string text line 2",
// including the two characters '\' and 'n'
此外,还存在 String.raw() 方法,用于创建原始字符串,就像默认模板函数和字符串连接会创建的那样。
const str = String.raw`Hi\n${2 + 3}!`;
// "Hi\\n5!"
str.length;
// 6
Array.from(str).join(",");
// "H,i,\\,n,5,!"
如果字面量不包含任何转义序列,String.raw 的作用就像一个“恒等”标签。如果你想要一个实际的恒等标签,它总是像字面量未加标签一样工作,你可以创建一个自定义函数,将“已处理的”(即,转义序列已处理)字面量数组传递给 String.raw,假装它们是原始字符串。
const identity = (strings, ...values) =>
String.raw({ raw: strings }, ...values);
console.log(identity`Hi\n${2 + 3}!`);
// Hi
// 5!
这对于许多对特定名称标记的字面量进行特殊处理的工具很有用。
const html = (strings, ...values) => String.raw({ raw: strings }, ...values);
// Some formatters will format this literal's content as HTML
const doc = html`<!doctype html>
<html lang="en-US">
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>`;
标签模板与转义序列
在普通模板字面量中,字符串字面量中的转义序列都允许使用。任何其他格式不正确的转义序列都是语法错误。这包括:
\后跟除0之外的任何十进制数字,或\0后跟十进制数字;例如\9和\07(这是一种已废弃的语法)\x后跟少于两个十六进制数字(包括无);例如\xz\u未后跟{且后跟少于四个十六进制数字(包括无);例如\uz\u{}包含无效的 Unicode 码点——它包含非十六进制数字,或其值大于10FFFF;例如\u{110000}和\u{z}
注意: \ 后跟其他字符,虽然它们可能没有用,因为没有被转义,但不是语法错误。
然而,这对于标签模板来说是个问题,因为除了“已处理的”字面量之外,它们还可以访问原始字面量(转义序列保持原样)。
标签模板允许嵌入任意字符串内容,其中转义序列可能遵循不同的语法。例如,我们通过 String.raw 在 JavaScript 中嵌入 LaTeX 源代码。我们希望仍然能够使用以 u 或 x 开头而不受 JavaScript 语法限制的 LaTeX 宏。因此,标签模板中移除了格式良好转义序列的语法限制。下面的示例使用 MathJax 在一个元素中渲染 LaTeX
const node = document.getElementById("formula");
MathJax.typesetClear([node]);
// Throws in older ECMAScript versions (ES2016 and earlier)
// SyntaxError: malformed Unicode character escape sequence
node.textContent = String.raw`$\underline{u}$`;
MathJax.typesetPromise([node]);
然而,非法转义序列仍然必须在“已处理的”表示中表示。它们将在“已处理的”数组中显示为 undefined 元素
function log(str) {
console.log("Cooked:", str[0]);
console.log("Raw:", str.raw[0]);
}
log`\unicode`;
// Cooked: undefined
// Raw: \unicode
请注意,转义序列限制仅从 标签 模板中移除,而不是从 无标签 模板字面量中移除
const bad = `bad escape sequence: \unicode`;
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # sec-template-literals |
浏览器兼容性
加载中…
另见
- 数字和字符串指南
StringString.raw()- 词法语法
- 深入 ES6:模板字符串 on hacks.mozilla.org (2015)