Skip to content

Commit

Permalink
增加模板字符串
Browse files Browse the repository at this point in the history
  • Loading branch information
jawil committed Feb 25, 2017
1 parent e614366 commit f82de64
Showing 1 changed file with 140 additions and 1 deletion.
141 changes: 140 additions & 1 deletion BOOK/(四):模板字符串.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

在上一篇文章中,我说过要写一篇风格迥异的新文章,在了解了迭代器和生成器后,是时候来品味一些不烧脑的简单知识,如果你们觉得太难了,还不快去啃犀牛书!

现在,就让我们从最简单的知识学起吧!
Expand Down Expand Up @@ -45,4 +44,144 @@ $("#warning").html(`

## 反撇号的未来

如粪土让他
当然,模板字符串也并非事事包揽:

* 它们不会为你自动转义特殊字符,为了避免[跨站脚本](http://www.techrepublic.com/blog/it-security/what-is-cross-site-scripting/)漏洞,你应当像拼接普通字符串时做的那样对非置信数据进行特殊处理。
* 它们无法很好地与[国际化库](https://yuilibrary.com/yui/docs/intl/)(可以帮助你面向不同用户提供不同的语言)相配合,模板字符串不会格式化特定语言的数字和日期,更别提同时使用不同语言的情况了。
* 它们不能替代模板引擎的地位,例如:[Mustache](https://mustache.github.io/)[Nunjucks](https://mozilla.github.io/nunjucks/)

模板字符串没有内建循环语法,所以你无法通过遍历数组来构建类似HTML中的表格,甚至它连条件语句都不支持。你当然可以使用模板套构(**template inception**)的方法实现,但在我看来这方法略显愚钝啊。

不过,**ES6****JS**开发者和库设计者提供了一个很好的衍生工具,你可以借助这一特性突破模板字符串的诸多限制,我们称之为标签模板(**tagged templates**)。

标签模板的语法非常简单,在模板字符串开始的反撇号前附加一个额外的标签即可。我们的第一个示例将添加一个**SaferHTML**标签,我们要用这个标签来解决上述的第一个限制:自动转义特殊字符。

请注意,**ES6**标准库不提供类似**SaferHTML**功能,我们将在下面自己来实现这个功能。

```
var message =
SaferHTML`<p>${bonk.sender} 向你示好。</p>`;
```
这里用到的标签是一个标识符**SaferHTML**;也可以使用属性值作为标签,例如:**SaferHTML.escape**;还可以是一个方法调用,例如:**SaferHTML.escape({unicodeControlCharacters: false})**。精确地说,任何**ES6**[成员表达式(MemberExpression)](https://tc39.github.io/ecma262/#sec-left-hand-side-expressions)或调用表达式(**CallExpression**)都可作为标签使用。

可以看出,无标签模板字符串简化了简单字符串拼接,标签模板则完全简化了函数调用!

上面的代码等效于:

```
var message =
SaferHTML(templateData, bonk.sender);
```

**templateData**是一个不可变数组,存储着模板所有的字符串部分,由JS引擎为我们创建。因为占位符将标签模板分割为两个字符串的部分,所以这个数组内含两个元素,形如[Object.freeze](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)(["", " has sent you a bonk."]

(事实上,**templateData**中还有一个属性,在这篇文章中我们不会用到,但是它是标签模板不可分割的一环:**templateData.raw**,它同样是一个数组,存储着标签模板中所有的字符串部分,如果我们查看源码将会发现,在这里是使用形如\n的转义序列分行,而在**templateData**中则为真正的新行,标准标签**String.raw**会用到这些原生字符串。)

如此一来,**SaferHTML**函数就可以有成千上万种方法来解析字符串和占位符。

在继续阅读以前,可能你苦苦思索到底用SaferHTML来做什么,然后着手尝试去实现它,归根结底,它只是一个函数,你可以在**Firefox**的开发者控制台里测试你的成果。

以下是一种可行的方案(在[gist](https://gist.github.com/jorendorff/1a17f69dbfaafa2304f0)中查看):

```
function SaferHTML(templateData) {
var s = templateData[0];
for (var i = 1; i < arguments.length; i++) {
var arg = String(arguments[i]);
// 转义占位符中的特殊字符。
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/</g, ">");
// 不转义模板中的特殊字符。
s += templateData[i];
}
return s;
}
```
通过这样的定义,标签模板SaferHTML<p>${bonk.sender} 向你示好。</p> 可能扩展为字符串 "ES6"。即使一个恶意命名的用户,例如“黑客Stevealert('xss');”,向其他用户发送一条骚扰信息,无论如何这条信息都会被转义为普通字符串,其他用户不会受到潜在攻击的威胁。

(顺便一提,如果你感觉上述代码中在函数内部使用[参数对象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments)的方式令你感到枯燥乏味,不妨期待下一篇大作,ES6中的另一个新特性一定会让你眼前一亮!)

仅一个简单的示例不足以说明标签模板的灵活性,我们一起回顾下我们之前有关模板字符串限制的列表,看一下你还能做些什么不一样的事情。

模板字符串不会自动转义特殊字符。但是正如我们看到的那样,通过标签模板,你可以自己写一个标签函数来解决这个问题。
事实上,你可以做的比那更好。

站在安全角度来说,我实现的SaferHTML函数相当脆弱,你需要通过多种不同的方式将**HTML**不同部分的特殊字符转义,**SaferHTML**就无法做到全部转义。但是稍加努力,你就可以写出一个更加智能的**SaferHTML**函数,它可以针对**templateData**中字符串中的HTML位进行解析,分析出哪一个占位符是纯**HTML**;哪一个是元素内部属性,需要转义'和";哪一个是**URL****query**字符串,需要进行URL转义而非HTML转义,等等。智能**SaferHTML**函数可以将每个占位符都正确转义。

**HTML**的解析速度很慢,这种方法听起来是否略显牵强?幸运的是,当模板重新求值的时候标签模板的字符串部分是不改变的。**SaferHTML**可以缓存所有的解析结果,来加速后续的调用。(缓存可以按照**ES6**的另一个特性——**WeakMap**的形式进行存储,我们将在未来的文章中继续深入讨论。)

* 模板字符串没有内建的国际化特性,但是通过标签,我们可以添加这些功能。**Jack Hsu**的一篇博客文章展示了具体的实现过程。我谨在此处抛砖引玉:

```
i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.`
// => Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto.
```
注意观察这个示例中的运行细节,**name****amount**都是**JavaScript**,进行正常插值处理,但是有一段与众不同的代码,**:c(CAD)****Jack**将它放入了模板的字符串部分。**JavaScript**理应由**JavaScript**引擎进行处理,字符串部分由**Jack****i18n**标签进行处理。使用者可以通过**i18n**的文档了解到,**:c(CAD)**代表加拿大元的货币单位。

这就是标签模板的大部分实际应用了。

* 模板字符串不能代替Mustache和Nunjucks,一部分原因是在模板字符串没有内建的循环或条件语句语法。我们一起来看如何解决这个问题,如果JS不提供这个特性,我们就写一个标签来提供相应支持。

```
// 基于纯粹虚构的模板语言
// ES6标签模板。
var libraryHtml = hashTemplate`
<ul>
#for book in ${myBooks}
<li><i>#{book.title}</i> by #{book.author}</li>
#end
</ul>
`;
```
标签模板带来的灵活性远不止于此,要记住,标签函数的参数不会自动转换为字符串,它们如返回值一样,可以是任何值,标签模板甚至不一定要是字符串!你可以用自定义的标签来创建正则表达式、**DOM**树、图片、以promises为代表的整个异步过程、**JS**数据结构、**GL**着色器……

**标签模板以开放的姿态欢迎库设计者们来创建强有力的领域特定语言。**这些语言可能看起来不像**JS**,但是它们仍可以无缝嵌入到**JS**中并与**JS**的其它语言特性智能交互。我不知道这一特性将会带领我们走向何方,但它蕴藏着无限的可能性,这令我感到异常兴奋!

## 我什么时候可以开始使用这一特性?

在服务器端,**io.js**支持**ES6**的模板字符串。

在浏览器端,**Firefox 34+**支持模板字符串。它们由去年夏天的实习生项目组里的G**uptha Rajagopal**实现。模板字符串同样在**Chrome 41+**中得以支持,但是**IE****Safari**都不支持。到目前为止,如果你想要在**web**端使用模板字符串的功能,你将需要[Babel](http://babeljs.io/)[Traceur](https://github.com/google/traceur-compiler#what-is-traceur)协助你完成ES6到ES5的代码转译,你也可以在[TypeScript](https://blogs.msdn.microsoft.com/typescript/2015/01/16/announcing-typescript-1-4/)中立即使用这一特性。

## 等等——那么Markdown呢?

嗯?

哦…这是个好问题。

(这一章节与**JavaScript**无关,如果你不使用[Markdown](http://daringfireball.net/projects/markdown/basics),可以跳过这一章。)

对于模板字符串而言,Markdown和**JavaScript**现在都使用`字符来表示一些特殊的事物。事实上,在**Markdown**中,反撇号用来分割在内联文本中间的代码片段。

这会带来许多问题!如果你在**Markdown**中写这样的文档:

```
To display a message, write `alert(`hello world!`)`.
```
它将这样显示:

```
To display a message, write alert(hello world!).
```
请注意,输出文本中的反撇号消失了。**Markdown**将所有的四个反撇号解释为代码分隔符并用**HTML**标签将其替换掉。

为了避免这样的情况发生,我们要借助**Markdown**中的一个鲜为人知的特性,你可以使用多行反撇号作为代码分隔符,就像这样:

```
To display a message, write ``alert(`hello world!`).
```
在这个[Gist](https://gist.github.com/jorendorff/d3df45120ef8e4a342e5)有具体代码细节,它由**Markdown**写成,所以你可以直接查看源代码。

## 下回预告

下一次,我们将要接触两个新特性,数十年以来它们深得其它语言程序员的喜爱:其中一个可以使开发者免于传参(使用默认参数),另一个可以帮助传非常多参数的开发者们管理他们的函数参数。这两个特性对我们所有人来说都非常有帮助!

客座作者**Benjamin Peterson****Firefox**中实现了这些特性,我们将透过他的视角来了解这些新特性,它将为我们深入解析**ES6**默认参数及不定参数。观众老爷们记得回来哦!我会想你们的!

上一篇:[(三):生成器 Generators](https://github.com/jawil/ES6/blob/master/BOOK/%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E7%94%9F%E6%88%90%E5%99%A8%20Generators.md)----------------------------下一篇:[(五):不定参数和默认参数]()


0 comments on commit f82de64

Please sign in to comment.