使用 ES6 範本字串取得常值

Addy Osmani
Addy Osmani

JavaScript 中的字串功能一直受到限制,缺乏 Python 或 Ruby 等語言的功能。但 ES6 範本字串 (適用於 Chrome 41 以上版本) 徹底改變了這個情況。這些 API 會介紹一種方法,讓您使用特定領域語言 (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!`

標記模板字串的語意與一般模板字串的語意截然不同。從本質上來說,這些是特殊類型的函式呼叫:上述「desugars」會轉換為

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> "&amp; 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 = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
    },
    oUnescape = {
        '&amp;': '&',
        '&#38;': '&',
        '&lt;': '<',
        '&#60;': '<',
        '&gt;': '>',
        '&#62;': '>',
        '&apos;': "'",
        '&#39;': "'",
        '&quot;': '"',
        '&#34;': '"'
    },
    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>: "&amp; 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 的重要功能。這些函式會接收範本字串的部分做為引數,您可以決定如何使用字串和替換項目,以決定字串的最終輸出內容。

延伸閱讀