模板字面量(模板字串)
模板字面量 是由反引號 (`) 字元包圍的字面量,允許使用多行字串、透過嵌入表示式實現字串插值,以及稱為標籤模板的特殊構造。
模板字面量有時也被非正式地稱為 模板字串,因為它們最常用於字串插值(透過替換佔位符來建立字串)。然而,標籤模板字面量可能不會生成字串;它可以與自定義的標籤函式一起使用,以對模板字面量的不同部分執行任何所需的操作。
語法
`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)