JavaScript 中的字串一直以來都受到限制,缺乏 Python 或 Ruby 等語言提供的功能。ES6 範本字串 (適用於 Chrome 41 以上版本) 從根本上改變了這種情況。這些函式提供使用網域特定語言 (DSL) 定義字串的方法,可帶來以下優點:
- 字串插值
- 嵌入的運算式
- 不含駭客的多行字串
- 字串格式設定
- 字串標記可確保 HTML 逸出安全無虞、進行本地化等。
範本字串並非在現有字串中加入另一項功能,而是以完全不同的方式解決這些問題。
語法
範本字串使用反引號 (``),而非一般字串常用的單引號或雙引號。因此,範本字串可以寫成如下形式:
var greeting = `Yo World!`;
到目前為止,樣板字串提供的功能與一般字串並無不同。讓我們改變這個情況。
字串替代
其中一項最先實現的實質效益是字串替代。替換功能可讓我們採用任何有效的 JavaScript 運算式 (包括變數加法),並在範本常值中輸出結果,做為同一字串的一部分。
範本字串可使用 ${ } 語法,包含字串替換的預留位置,如下所示:
// Simple string substitution
var name = "Brendan";
console.log(`Yo, ${name}!`);
// => "Yo, Brendan!"
由於範本字串中的所有字串替代項目都是 JavaScript 運算式,因此我們可替代的項目遠不只變數名稱。舉例來說,我們可以在下方使用運算式插補,嵌入一些可讀的內嵌數學式:
var a = 10;
var b = 10;
console.log(`JavaScript first appeared ${a+b} years ago. Wow!`);
//=> JavaScript first appeared 20 years ago. Wow!
console.log(`The number of JS MVC frameworks is ${2 * (a + b)} and not ${10 * (a + b)}.`);
//=> The number of JS frameworks is 40 and not 200.
對於運算式中的函式,這也很有用:
function fn() { return "I am a result. Rarr"; }
console.log(`foo ${fn()} bar`);
//=> foo I am a result. Rarr bar.
${} 可搭配任何類型的運算式,包括成員運算式和方法呼叫:
var user = {name: 'Caitlin Potter'};
console.log(`Thanks for getting this into V8, ${user.name.toUpperCase()}.`);
// => "Thanks for getting this into V8, CAITLIN POTTER";
// And another example
var thing = 'template strings';
console.log(`Say hello to ${thing}.`);
// => Say hello to template strings
如果字串中需要反引號,可以使用反斜線字元 \ 逸出,如下所示:
var greeting = `\`Yo\` World!`;
多行字串
在 JavaScript 中,多行字串需要使用一些變通方法。目前解決方案要求字串必須位於單行,或使用 \ (反斜線) 將字串分割成多行,且每行都必須換行。例如:
var greeting = "Yo \
World";
雖然這在大多數現代 JavaScript 引擎中都能正常運作,但這種行為本身仍有點像是變通做法。也可以使用字串串連來模擬多行支援,但這同樣不盡理想:
var greeting = "Yo " +
"World";
範本字串可大幅簡化多行字串。只要在需要換行的地方加入換行符,即可完成。範例如下:
反引號語法內的任何空白字元也會視為字串的一部分。
console.log(`string text line 1
string text line 2`);
已標記範本
到目前為止,我們已瞭解如何使用範本字串進行字串替換,以及建立多行字串。標記範本是他們帶來的另一項強大功能。標記範本會在範本字串前加上函式名稱,藉此轉換範本字串。例如:
fn`Hello ${you}! You're looking ${adjective} today!`
標記範本字串的語意與一般字串的語意大不相同。從本質上來說,這是一種特殊類型的函式呼叫:上述「desugar」會變成
fn(["Hello ", "! You're looking ", " today!"], you, adjective);
請注意,第 (n + 1) 個引數對應於字串陣列中第 n 個和第 (n + 1) 個項目之間發生的替代。這項功能可用於各種用途,但最直接的用途之一,就是自動逸出任何插補變數。
舉例來說,您可以編寫 HTML 逸出函式,以便...
html`<p title="${title}">Hello ${you}!</p>`
傳回的字串會代入適當的變數,但所有不安全的 HTML 字元都會替換。我們來看看吧!我們的 HTML 逸出函式會採用兩個引數:使用者名稱和留言。兩者都可能包含 HTML 不安全字元 (即 '、"、<、> 和 &)。舉例來說,如果使用者名稱是「Domenic Denicola」,留言是「& is a fun tag」,我們應輸出:
<b>Domenic Denicola says:</b> "& is a fun tag"
因此,我們可將標記範本解決方案編寫如下:
// HTML Escape helper utility
var util = (function () {
// Thanks to Andrea Giammarchi
var
reEscape = /[&<>'"]/g,
reUnescape = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g,
oEscape = {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
},
oUnescape = {
'&': '&',
'&': '&',
'<': '<',
'<': '<',
'>': '>',
'>': '>',
''': "'",
''': "'",
'"': '"',
'"': '"'
},
fnEscape = function (m) {
return oEscape[m];
},
fnUnescape = function (m) {
return oUnescape[m];
},
replace = String.prototype.replace
;
return (Object.freeze || Object)({
escape: function escape(s) {
return replace.call(s, reEscape, fnEscape);
},
unescape: function unescape(s) {
return replace.call(s, reUnescape, fnUnescape);
}
});
}());
// Tagged template function
function html(pieces) {
var result = pieces[0];
var substitutions = [].slice.call(arguments, 1);
for (var i = 0; i < substitutions.length; ++i) {
result += util.escape(substitutions[i]) + pieces[i + 1];
}
return result;
}
var username = "Domenic Denicola";
var tag = "& is a fun tag";
console.log(html`<b>${username} says</b>: "${tag}"`);
//=> <b>Domenic Denicola says</b>: "& is a fun tag"
其他可能用途包括自動逸出、格式化、本地化,以及更複雜的替換作業:
// Contextual auto-escaping
qsa`.${className}`;
safehtml`<a href="${url}?q=${query}" onclick="alert('${message}')" style="color: ${color}">${message}</a>`;
// Localization and formatting
l10n`Hello ${name}; you are visitor number ${visitor}:n! You have ${money}:c in your account!`
// Embedded HTML/XML
jsx`<a href="${url}">${text}</a>` // becomes React.DOM.a({ href: url }, text)
// DSLs for code execution
var childProcess = sh`ps ax | grep ${pid}`;
摘要
Chrome 41 Beta 版以上、IE 技術預覽版、Firefox 35 以上版本和 io.js 都支援範本字串。實際上,如果您想在正式環境中使用這些功能,主要 ES6 轉譯器 (包括 Traceur 和 6to5) 均支援這些功能。如要試用範本字串,請前往 Chrome 範例存放區查看範本字串範例。您可能也會對 ES5 中的 ES6 對等項目感興趣,這項功能示範如何使用 ES5 達成範本字串帶來的部分糖化效果。
樣板字串為 JavaScript 帶來許多重要功能。包括更完善的字串和運算式插補方式、多行字串,以及建立專屬 DSL 的功能。
其中一項重要功能是標記範本,這是撰寫這類 DSL 的關鍵功能。這些函式會將範本字串的各個部分當做引數接收,然後您可以決定如何使用字串和替代項目,判斷字串的最終輸出內容。