使用 ES6 模板字符串获取字面量

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!`

带标记的模板字符串的语义与普通模板字符串的语义有很大不同。从本质上讲,它们是一种特殊的函数调用:上述代码“反糖化”为

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 中使用。实际上,如果您想在生产环境中立即使用它们,那么包括 Traceur 和 6to5 在内的主要 ES6 转译器都支持它们。如果您想试用模板字符串,请查看 Chrome 示例代码库中的模板字符串示例。您可能还会对 ES5 中的 ES6 等效项感兴趣,该文章演示了如何使用 ES5 实现模板字符串带来的部分语法糖。

模板字符串为 JavaScript 带来了许多重要功能。这些功能包括更出色的字符串和表达式插值、多行字符串以及创建自定义 DSL 的功能。

它们带来的最重要的功能之一是标记的模板,这是编写此类 DSL 的关键功能。它们会接收模板字符串的各个部分作为实参,然后您可以决定如何使用这些字符串和替换项来确定字符串的最终输出。

延伸阅读