ES6 in Depth: Template Strings
上周,我们已经提到要变步伐。在迭代器和生成器之后,我们来开始些简单的,这是我之前说过的。我也说过,这些是你未曾遇到过的。那,我们来看这承诺是否能够保持吧。
现在,让我们开始些简单的。
音符基础
ES6 推出了一种新的字符串语法,叫做模板字符串。他们看起来很像普通的字符串,除了其使用了音符式的字符,而不仅仅是'
和"
这些引号字符。对于最简单的情况,他们就是字符串:
1
|
|
但是,之所以称为模板字符串,而不是又普通又老的、仅包含音符的字符串,是因为模板字符串为JS提供了字符串的插入操作。这为JS提供了种好看、且方便的方法来将值插入到字符串中。
错误提示可以有上千百万的方法来实现,但是下面是我内心比较喜欢的:
1 2 3 4 5 6 |
|
在这个例子中,${user.name}
和${action}
叫做 模板替换。JS 会将user.name
和action
的值插入到最终的字符串中。这可能会产生一个消息如:User jorendorff is not authorized to do hockey.
目前,这相对于+
的拼接操作更显得优雅点,你可能还会希望其详细如下:
代码中的模板替换可以是任意的JS表达式,如 函数调用、算法等等都是允许的。(或许,你甚至还希望能在一个模板字符串中肉插入另一个模板字符串,我称为模板插入。)
如果值不是字符串时,它会使用常用的规则转为字符串。例如,如果
action
是个对象,那么.toString()
方法将会被调用。如果你需要在模板字符串中写入音符号,你必须避免直接用,而是使用斜杠:
\'
,其会为”’”。同样的,如果你在模板字符串中需要包含
${
这两个字符,我不知道你想怎么实现,但是你可以使用write \${
和$\{
来规避。
不像普通的字符串,模板字符串可以进行多行:
1 2 3 |
|
所有模板字符串中的空字符,包括换行符和缩进,都会逐个输出的。
好了。由于我之前在上周的承诺,我感到对你们的大脑需要负一定的责任。所以,先在这说明:我心里有些许的紧张。你现在可以停止往下读,或者应该拿着一杯咖啡、好好享受一下完整而未被融化的大脑。真的,返回去并不丢脸。当 Lopes Goncalves 得到一艘海里怪兽攻击、不被暗礁打翻的船后,他是不是探索完了整个南半球?不,他返回,回家了,然后还吃了顿可口的午餐。你喜欢吃午餐,对不对?
音符的未来
让我们讨论一下新的东西,模板字符串不做什么:
它们不会自动帮你处理特殊字符。为了避免跨域网站的脚本攻击,你将不得不小心地处理非信任的数据,就如你拼接普通的字符串。
它不直接作用于国际化库(对于不同的用户使用不同的语言库)。模板字符串不会处理特定语言的数字和日期的格式,更不用说复数了。
它们并不是模板库的替代品,如 Mustanche 和 Nunjucks。模板字符串没有任何内置的循环语法–例如,利用一数组来构建HTML中table的多行,或者条件。(是的,你可以使用模板插入来做到这目的,但对我来说,你那样做看起来就是是排序,是个笑话。)
ES6 为模板字符串不仅仅提供了一种方法,其可让JS开发人员和库的设计人员更多的能量来解决这些限制或者更多。这一特性特为 标签模板。
标签模板的语法很简单,它只是在模板字符串打开重音符之前加入额外的标签,这个标签是SaferHTML
,是合法的html字符。然后,我们就可以使用这个标签来解决之前的第一条的限制:自动处理特殊字符。
注意,SaferHTML
并不是ES6标准库中的某些东西,而是要我们自己来实现的,
1 2 |
|
这标签有个简单的身份:SaferHTML,但是一个标签也可以是一个属性,例如 SaferHTML.escape
,或者甚至是一个方法调用,如SaterHTML.escape({unicodeControlCharacters:false})
。(准确来说,任何的 MemberExpression or CallExpression 都可以作为一个标签。)
我们已经看到,非标签的模板字符串对于简单的字符串拼接是比较快捷的,标签模板对于某些更为便捷的是:函数调用。
之前的代码等价于:
1 2 |
|
templateData
是一个不可变的数组,它包括了由JS引擎替我们生成的模板所有字符串的各部分。这里,这个数组有两个元素,因为这两部分是由替代的标签来分隔的,所以templateData
会如Object.freeze(["<p>"," has seet you a bonk.</p>"])
。
(实际上,还有一个额外的属性出现在templateData
中。我们不会在此文章中使用到,但是,为了完整性,我将进行些说明:templdateData.raw
是标签模板中的另外一个包含所有字符串各部分的数组,它看起来像代码中在每部分的左边增加多类如\n
的符号,而不直接另外换行,等等。标准的标签 String.raw 就是使用这种字符串。)
这里给了方便的SaferHTML
方法来解释字上百万种的符串和替代物。
阅读了以上部分时,你也许会对SaferHTML
做什么感到困扰,然后,你试着来实现它吧。毕竟,这只是一个函数而已。你可以在 Firefox 的开发者控制台进行调试。
这里,可能的一个答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
通过这个定义,标签模板SaferHTML <p>${bonk.sender} has sent you a bonk.</p>
会输出为<p>ES6<3er has sent you a bonk.</p>
(这结果怎么看都不对啊,作者怎么玩的)。你用户在使用时它都是安全的,即使用户是恶意的用户,如Hacker Steve <script>alert('xss');</script>
这样发送一个炸弹。无论代表什么意思。
(顺便提一下,如果函数使用 arguments 对象 来攻击你时确实是个问题,下周会解决掉。这是ES6的另一个新特性,我想你会喜欢的。)
一个简单的例子是不能够说明标签模板其灵活性的,让我们重温一下之前我的提到的模板字符串的短板吧,而现在又可以怎么做:
- 模板字符串不能自动过滤特殊字符,但是,就如我们看到的,利用标签模板,你使用一个标签来解决这一问题。
实际上,你可以比上面做得更好。
从安全的角度上看,我的SaferHTML
函数是十分脆弱的。在HTML中,不同的地方有不同的特殊字符,需求不同的过滤方式,SaferHTML
并不能过滤掉所有。但是,通过努力,你可以编写更为智能的SafterHTML
函数来对templdateData
字符串中的部分HTML,这样子可以让其知道在HTML中哪个是替代物;哪些是内置的元素属性,需要过滤'
和"
;哪些是URL查询字符串,需要用URL过滤,而不是HTML过滤,等等。这样子,才能正确处理好每个替代物。
会不会由于HTML的解释速度慢而使目标难到达呢?幸运的是,当标签模板重新执行时,其字符串块并不会改变。SafteHTML 可以将解释结果进行缓存,以提升之后调用的速度。(这缓存可以是 WeakMap, 这是另外的ES6特性,我们将会在以后的文章进行讨论。)
- 模板字符串没有内置的国际化特性。但是,利用标签,我们可以实现。文章 展示了怎么实现的第一步。下面是个例子,可当作小玩意:
1 2 |
|
注意,现在这个例子,name
和amount
是属于Javascript,但是这里又有些不同的不熟悉的代码,那就是:c(CAD)
,这就是模板中需要替换的字符串部分。Javascript部分由JS的引擎控制,那个字符串部分由 Jack 的 i18n 的标签来控制。用户可以从 i18n 的文档中知道 :c(CAD)
表示amount
数量的货币,叫做加元。
这就是标签模板的作用。
- 模板字符串不是Mustache和Nunjucks的替代品,一定程度上是因为它没有内置的循环或条件的语法。但是现在,让我们看看你可以怎么实现它。如果JS没有提供这一特性,那么可以写一个标签来达到这目的。
1 2 3 4 5 6 7 8 9 |
|
其灵活性不止于此。注意,标签函数中的参数并不会自动转为字符串,它们可以是任何类型,返回值也是一样。标签模板甚至并不必要是字符串!你可以使用自定义的标签来实现一定的表达式,DOM 树、图片、代表全部异步进程的Promise、JS的数据结构、GL着色器(GL shaders)…
标签模板欢迎库设计者来创建强大的领域语言。这语言可能看起来并不像JS,但是它们可以无缝地嵌于JS中,并智能地与其它语言相互影响。不负责地说,我不能想象有其它语言可以做到这点。我说不出这特性会带给我们什么,这可能性却是令人兴奋的。
什么时候我可能开始使用?
在服务端,io.js现在已经支持ES6的模板字符串了。
在浏览器,Firefox 34+ 支持模板字符串,它由Guptha Rajagopal 在去年夏天的一个内部项目中实现的。模板字符串还得到 Chrome 41+ 的支持,但是不是包括 IE 和 Safari。现在,如果你需要在网站中使用模板字符串时,你将需要 Babel 或者 Traceur (来转化为ES5)。你现在还可以使用 TypeScript。
什么是Markdown?
Hmm?
好问题。
(这部分并不真正与JS有关,如果你不使用Markdown,你可以跳过这部分。)
对于模板字符串,Markdown和JS现在都使用 ` 符号来代码特殊含义。实际上,在Markdown中,它是内容中的代码界定符号。
如果你写以下Markdown文档时,这会带来些问题:
1
|
|
这会显示为:
To display a message, write `alert(`hello world!`)`.
注意,这里并没有符号输出,Markdown 解释器会将四个空格认为是代码的界定符号,然后使用HTML的标签来替换它。
为了避免这问题,我们在开始时了解到Markdown些特性:你可以多个符号来作为代码的界定符号,如:
1
|
|
Gist 有更为详细的内容,它是用Markdown来编写的,你可以看到其源码。
下一步
下周,我们将着手这数十年来程序大大在其它语言十分乐道的两个特性:一、大家喜欢尽可能不写没必要的参数;二、部分人喜欢很多的参数。好吧,我说的是就是函数的参数,这特性是我们所有人都需要知道的。
我们将通过 Firefox中这一特性的作者的角度来了解这些特性,所以,请在下周加入我们吧,猜测一下,其作者 Benjamin Peterson 是怎么深入 ES6的默认参数和剩余参数。