使用 ES6 範本字串取得常值

阿迪奧斯馬尼
Addy Osmani

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";

範本字串能大幅簡化多行字串。只要在內容中加入新的一行或 BOOM 即可。範例如下:

倒引號語法中的任何空白字元都會視為字串的一部分。

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

標記範本

到目前為止,我們已介紹如何使用範本字串替換字串,以及建立多行字串。他們提供的另一項強大功能就是標記範本。標記範本會將函式名稱置於範本字串前方,以轉換範本字串。例如:

fn`Hello ${you}! You're looking ${adjective} today!`

標記範本字串的語意與一般變數非常不同。基本上,它們是特殊的函式呼叫類型:上述的「脫糖」

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」,而註解為「& 是有趣標記」,則我們可以輸出:

<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 Transpilers,包括 Traceur 和 6to5。如要試用,請前往 Chrome 範例存放區查看範本字串範例。您或許也會想查看 ES5 的 ES6 對等項目,其中示範如何建立目前使用 ES5 帶來的一些糖類範本字串。

範本字串為 JavaScript 提供了許多重要功能。包括更妥善地進行字串和運算式內插、多行字串,以及建立自己的 DSL。

他們提供的重要功能之一就是標記範本,是編寫這類 DSL 的重要功能。這些變數會接收範本字串的部分做為引數,然後您就可以決定如何使用字串和替代字詞來決定字串的最終輸出結果。

延伸閱讀