模板文字(模板字符串)

模板文字是用反引号 (```) 字符分隔的文字,允许使用嵌入表达式的 多行字符串字符串插值 以及称为 标记模板 的特殊构造。

¥Template literals are literals delimited with backtick (`) characters, allowing for multi-line strings, string interpolation with embedded expressions, and special constructs called tagged templates.

模板文字有时被非正式地称为模板字符串,因为它们最常用于 字符串插值(通过替换占位符来创建字符串)。然而,带标签的模板文字可能不会产生字符串;它可以与自定义 标签功能 一起使用,以对模板文字的不同部分执行你想要的任何操作。

¥Template literals are sometimes informally called template strings, because they are used most commonly for string interpolation (to create strings by doing substitution of placeholders). However, a tagged template literal may not result in a string; it can be used with a custom tag function to perform whatever operations you want on the different parts of the template literal.

语法

¥Syntax

js
`string text`

`string text line 1
 string text line 2`

`string text ${expression} string text`

tagFunction`string text ${expression} string text`

参数

¥Parameters

string text

将成为模板文字一部分的字符串文本。几乎所有字符都允许按字面意思使用,包括 换行符 和其他 空白字符。但是,无效的转义序列将导致语法错误,除非使用 标签功能

expression

当前位置要插入的表达式,其值被转换为字符串或传递给 tagFunction

tagFunction

如果指定,它将使用模板字符串数组和替换表达式来调用,并且返回值成为模板文字的值。参见 标记模板

描述

¥Description

模板文字用反引号 (```) 字符而不是双引号或单引号括起来。

¥Template literals are enclosed by backtick (`) characters instead of double or single quotes.

除了普通字符串之外,模板文字还可以包含称为占位符的其他部分,这些部分是由美元符号和大括号分隔的嵌入表达式:${expression}。字符串和占位符被传递给一个函数 - 要么是默认函数,要么是你提供的函数。默认函数(当你不提供自己的函数时)仅执行 字符串插值 来替换占位符,然后将各部分连接成单个字符串。

¥Along with having normal strings, template literals can also contain other parts called placeholders, which are embedded expressions delimited by a dollar sign and curly braces: ${expression}. The strings and placeholders get passed to a function — either a default function, or a function you supply. The default function (when you don't supply your own) just performs string interpolation to do substitution of the placeholders and then concatenate the parts into a single string.

要提供你自己的函数,请在模板文字前面加上函数名称;结果称为 标记模板。在这种情况下,模板文字将传递到标记函数,然后你可以在其中对模板文字的不同部分执行所需的任何操作。

¥To supply a function of your own, precede the template literal with a function name; the result is called a tagged template. In that case, the template literal is passed to your tag function, where you can then perform whatever operations you want on the different parts of the template literal.

要转义模板文字中的反引号,请在反引号前添加反斜杠 (\)。

¥To escape a backtick in a template literal, put a backslash (\) before the backtick.

js
`\`` === "`"; // true

美元符号也可以被转义以防止插值。

¥Dollar signs can be escaped as well to prevent interpolation.

js
`\${1}` === "${1}"; // true

多行字符串

¥Multi-line strings

源中插入的任何换行符都是模板文字的一部分。

¥Any newline characters inserted in the source are part of the template literal.

使用普通字符串,你必须使用以下语法才能获取多行字符串:

¥Using normal strings, you would have to use the following syntax in order to get multi-line strings:

js
console.log("string text line 1\n" + "string text line 2");
// "string text line 1
// string text line 2"

使用模板文字,你可以执行相同的操作:

¥Using template literals, you can do the same with this:

js
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"

字符串插值

¥String interpolation

如果没有模板文字,当你想要将表达式的输出与字符串组合时,你可以使用 加法运算符 + 将它们连接起来

¥Without template literals, when you want to combine output from expressions with strings, you'd concatenate them using the addition operator +:

js
const a = 5;
const b = 10;
console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
// "Fifteen is 15 and
// not 20."

这可能很难阅读 - 尤其是当你有多个表达式时。

¥That can be hard to read – especially when you have multiple expressions.

使用模板文字,你可以通过使用 ${expression} 形式的占位符来执行嵌入表达式的替换,从而避免使用连接运算符,并提高代码的可读性:

¥With template literals, you can avoid the concatenation operator — and improve the readability of your code — by using placeholders of the form ${expression} to perform substitutions for embedded expressions:

js
const a = 5;
const b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

请注意,两种语法之间存在轻微差异。模板文字 将它们的表达式直接强制转换为字符串,而加法首先将其操作数强制为原语。有关详细信息,请参阅 + 运算符 的参考页。

¥Note that there's a mild difference between the two syntaxes. Template literals coerce their expressions directly to strings, while addition coerces its operands to primitives first. For more information, see the reference page for the + operator.

嵌套模板

¥Nesting templates

在某些情况下,嵌套模板是获得可配置字符串的最简单(也许更易读)的方法。在反引号分隔的模板中,通过在模板内的 ${expression} 占位符内使用内部反引号可以很简单地允许内部反引号。

¥In certain cases, nesting a template is the easiest (and perhaps more readable) way to have configurable strings. Within a backtick-delimited template, it is simple to allow inner backticks by using them inside an ${expression} placeholder within the template.

例如,在没有模板文字的情况下,如果你想根据特定条件返回某个值,你可以执行如下操作:

¥For example, without template literals, if you wanted to return a certain value based on a particular condition, you could do something like the following:

js
let classes = "header";
classes += isLargeScreen()
  ? ""
  : item.isCollapsed
    ? " icon-expander"
    : " icon-collapser";

使用模板文字但没有嵌套,你可以这样做:

¥With a template literal but without nesting, you could do this:

js
const classes = `header ${
  isLargeScreen() ? "" : item.isCollapsed ? "icon-expander" : "icon-collapser"
}`;

通过嵌套模板文字,你可以执行以下操作:

¥With nesting of template literals, you can do this:

js
const classes = `header ${
  isLargeScreen() ? "" : `icon-${item.isCollapsed ? "expander" : "collapser"}`
}`;

标记模板

¥Tagged templates

模板文字的更高级形式是标记模板。

¥A more advanced form of template literals are tagged templates.

标签允许你使用函数解析模板文字。标签函数的第一个参数包含一个字符串值数组。其余参数与表达式相关。

¥Tags allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions.

然后,标记函数可以对这些参数执行你希望的任何操作,并返回操作后的字符串。(或者,它可以返回完全不同的内容,如以下示例之一所述。)

¥The tag function can then perform whatever operations on these arguments you wish, and return the manipulated string. (Alternatively, it can return something completely different, as described in one of the following examples.)

用于标记的函数名称可以是你想要的任何名称。

¥The name of the function used for the tag can be whatever you want.

js
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.

标签不必是普通标识符。你可以使用 precedence 大于 16 的任何表达式,其中包括 属性访问权、函数调用、新的表达方式,甚至另一个标记模板文字。

¥The tag does not have to be a plain identifier. You can use any expression with precedence greater than 16, which includes property access, function call, new expression, or even another tagged template literal.

js
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

¥While technically permitted by the syntax, untagged template literals are strings and will throw a TypeError when chained.

js
console.log(`Hello``World`); // TypeError: "Hello" is not a function

唯一的例外是可选链接,这会引发语法错误。

¥The only exception is optional chaining, which will throw a syntax error.

js
console.log?.`Hello`; // SyntaxError: Invalid tagged template on optional chain
console?.log`Hello`; // SyntaxError: Invalid tagged template on optional chain

请注意,这两个表达式仍然是可解析的。这意味着它们不会受到 自动插入分号 的约束,自动插入分号 只会插入分号来修复无法解析的代码。

¥Note that these two expressions are still parsable. This means they would not be subject to automatic semicolon insertion, which will only insert semicolons to fix code that's otherwise unparsable.

js
// Still a syntax error
const a = console?.log
`Hello`

标签函数甚至不需要返回字符串!

¥Tag functions don't even need to return a string!

js
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."

标签函数接收的第一个参数是字符串数组。对于任何模板文字,其长度等于替换次数(出现 ${…})加一,因此始终非空。

¥The first argument received by the tag function is an array of strings. For any template literal, its length is equal to the number of substitutions (occurrences of ${…}) plus one, and is therefore always non-empty.

对于任何特定的标记模板文字表达式,无论该文字被计算多少次,都将始终使用完全相同的文字数组调用标签函数。

¥For any particular tagged template literal expression, the tag function will always be called with the exact same literal array, no matter how many times the literal is evaluated.

js
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 属性 都是 frozen,因此你不能以任何方式改变它们。

¥This allows the tag to cache the result based on the identity of its first argument. To further ensure the array value's stability, the first argument and its raw property are both frozen, so you can't mutate them in any way.

原始字符串

¥Raw strings

特殊的 raw 属性在标记函数的第一个参数上可用,允许你访问输入的原始字符串,而无需处理 转义序列

¥The special raw property, available on the first argument to the tag function, allows you to access the raw strings as they were entered, without processing escape sequences.

js
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() 方法来创建原始字符串,就像默认模板函数和字符串连接所创建的那样。

¥In addition, the String.raw() method exists to create raw strings just like the default template function and string concatenation would create.

js
const str = String.raw`Hi\n${2 + 3}!`;
// "Hi\\n5!"

str.length;
// 6

Array.from(str).join(",");
// "H,i,\\,n,5,!"

如果文字不包含任何转义序列,则 String.raw 的功能类似于 "identity" 标记。如果你想要一个始终像未标记文字一样工作的实际身份标记,你可以创建一个自定义函数,将 "cooked"(即处理转义序列)文字数组传递给 String.raw,假装它们是原始字符串。

¥String.raw functions like an "identity" tag if the literal doesn't contain any escape sequences. In case you want an actual identity tag that always works as if the literal is untagged, you can make a custom function that passes the "cooked" (i.e. escape sequences are processed) literal array to String.raw, pretending they are raw strings.

js
const identity = (strings, ...values) =>
  String.raw({ raw: strings }, ...values);
console.log(identity`Hi\n${2 + 3}!`);
// Hi
// 5!

这对于许多对由特定名称标记的文字进行特殊处理的工具很有用。

¥This is useful for many tools which give special treatment to literals tagged by a particular name.

js
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>`;

标记模板和转义序列

¥Tagged templates and escape sequences

在普通模板文字中,字符串文字中的转义序列 都是允许的。任何其他格式不正确的转义序列都是语法错误。这包括:

¥In normal template literals, the escape sequences in string literals are all allowed. Any other non-well-formed escape sequence is a syntax error. This includes:

  • \ 后跟除 0 之外的任何十进制数字,或 \0 后跟十进制数字;例如 \9\07(即 不推荐使用的语法
  • \x 后跟少于两个十六进制数字(包括没有);例如 \xz
  • \u 后面没有 { 并且后面的十六进制数字少于四个(包括没有);例如 \uz
  • \u{} 包含无效的 Unicode 代码点 — 它包含非十六进制数字,或其值大于 10FFFF;例如 \u{110000}\u{z}

注意:\ 后面跟着其他字符,虽然它们可能没有用,因为没有转义任何内容,但它们不是语法错误。

¥Note: \ followed by other characters, while they may be useless since nothing is escaped, are not syntax errors.

然而,这对于标记模板来说是有问题的,除了 "cooked" 文字之外,标记模板还可以访问原始文字(转义序列按原样保留)。

¥However, this is problematic for tagged templates, which, in addition to the "cooked" literal, also have access to the raw literals (escape sequences are preserved as-is).

标记模板允许嵌入任意字符串内容,其中转义序列可能遵循不同的语法。考虑一个简单的示例,我们通过 String.rawLaTeX 源文本嵌入 JavaScript。我们希望仍然能够使用以 ux 开头的 LaTeX 宏,而无需遵循 JavaScript 语法限制。因此,从标记模板中删除了格式正确的转义序列的语法限制。以下示例使用 MathJax 在一个元素中呈现 LaTeX:

¥Tagged templates enable the embedding of arbitrary string content, where escape sequences may follow a different syntax. Consider for a simple example where we embed LaTeX source text in JavaScript via String.raw. We want to still be able to use LaTeX macros that start with u or x without following JavaScript syntax restrictions. Therefore, the syntax restriction of well-formed escape sequences is removed from tagged templates. The example below uses MathJax to render LaTeX in one element:

js
const node = document.getElementById("formula");
MathJax.typesetClear([node]);
// Throws in older ECMAScript versions (ES2016 and earlier)
// SyntaxError: malformed Unicode character escape sequence
node.innerHTML = String.raw`$\underline{u}$`;
MathJax.typesetPromise([node]);

但是,非法转义序列仍必须以 "cooked" 表示形式表示。它们将在 "cooked" 数组中显示为 undefined 元素:

¥However, illegal escape sequences must still be represented in the "cooked" representation. They will show up as undefined element in the "cooked" array:

js
function log(str) {
  console.log("Cooked:", str[0]);
  console.log("Raw:", str.raw[0]);
}

log`\unicode`;
// Cooked: undefined
// Raw: \unicode

请注意,转义序列限制仅从标记的模板中删除,但不会从未标记的模板文字中删除:

¥Note that the escape-sequence restriction is only dropped from tagged templates, but not from untagged template literals:

js
const bad = `bad escape sequence: \unicode`;

规范

Specification
ECMAScript Language Specification
# sec-template-literals

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看

¥See also