ES6 深入浅出 是一个系列文章,介绍了在 ECMAScript 标准第 6 版(简称 ES6)中添加的 JavaScript 编程语言的新功能。
上周,我承诺要改变一下节奏。我说,在介绍了 迭代器 和 生成器 之后,我们将着手一些简单的东西。我说了,一些不会让你头疼的东西。我们最后看看我是否能兑现诺言。
现在,让我们从简单的东西开始。
反引号基础
ES6 引入了一种新的字符串字面量语法,称为 模板字符串。它们看起来像普通字符串,只是使用反引号字符 `
而不是通常的引号 '
或 "
。在最简单的情况下,它们实际上只是字符串。
context.fillText(`Ceci n'est pas une chaîne.`, x, y);
但是,它们被称为“模板字符串”,而不是“无聊的普通字符串,什么特殊功能都没有,只是使用反引号”,是有原因的。模板字符串为 JavaScript 带来了简单的 字符串插值。也就是说,它们是一种外观良好、方便的方法,可以将 JavaScript 值插入字符串中。
有无数种方法可以利用它,但我最喜欢的一种是简洁的错误信息
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
`User ${user.name} is not authorized to do ${action}.`);
}
}
在这个例子中,${user.name}
和 ${action}
被称为 模板替换。JavaScript 将将 user.name
和 action
的值插入到结果字符串中。这可能会生成一条类似 用户 jorendorff 无权执行曲棍球操作。
的消息(这是真的。我没有曲棍球许可证。)
到目前为止,这仅仅是对 +
运算符的一种更友好的语法,而细节如你所想
- 模板替换中的代码可以是任何 JavaScript 表达式,因此允许函数调用、算术运算等等。(如果你真的想,你甚至可以在另一个模板字符串中嵌套模板字符串,我称之为 模板始祖。)
- 如果任一值不是字符串,它将使用通常的规则转换为字符串。例如,如果
action
是一个对象,它的.toString()
方法将被调用。 - 如果你需要在模板字符串中写入反引号,你必须使用反斜杠对其进行转义:
`\``
等同于"`"
。 - 同样,如果你需要在模板字符串中包含
${
这两个字符,我不知道你在做什么,但你可以使用反斜杠转义任一字符:`write \${ or $\{`
。
与普通字符串不同,模板字符串可以跨越多行
$("#warning").html(`
<h1>Watch out!</h1>
<p>Unauthorized hockeying can result in penalties
of up to ${maxPenalty} minutes.</p>
`);
模板字符串中的所有空格,包括换行符和缩进,都会原封不动地包含在输出中。
好的。由于上周的承诺,我觉得有责任保护你的大脑健康。所以,快速提醒一下:从这里开始会有点复杂。你现在可以停止阅读,也许去喝杯咖啡,享受你完整、没有融化的脑子。说真的,没有必要退缩。洛佩斯·冈萨雷斯 在证明船只可以穿越赤道而不会被海怪压碎或掉下地球边缘之后,是否对整个南半球进行了详尽的探索?没有。他转身回家,吃了一顿美餐。你喜欢午餐,对吧?
反引号未来
让我们谈谈模板字符串不做的事情。
- 它们不会自动为你转义特殊字符。为了避免 跨站点脚本 漏洞,你仍然必须小心处理不受信任的数据,就像你在连接普通字符串时一样。
- 它们如何与 国际化库(一个帮助你的代码对不同的用户说不同语言的库)交互并不明显。模板字符串不处理数字和日期的语言特定格式,更不用说复数了。
- 它们不能替代模板库,例如 Mustache 或 Nunjucks。
模板字符串没有内置的循环语法——例如,从数组构建 HTML 表格的行——甚至条件语句。(是的,你可以为此使用模板始祖,但对我来说,这似乎是你在开玩笑的时候会做的事情。)
ES6 为模板字符串提供了一种更强大的功能,使 JS 开发人员和库设计人员能够解决这些限制以及更多问题。这个功能叫做 标记模板。
标记模板的语法很简单。它们只是在开始反引号之前加上了一个额外的 标签 的模板字符串。在我们的第一个例子中,标签将是 SaferHTML
,我们将使用这个标签来尝试解决上面列出的第一个限制:自动转义特殊字符。
请注意,SaferHTML
不是 ES6 标准库提供的任何东西。我们将在下面自己实现它。
var message =
SaferHTML`<p>${bonk.sender} has sent you a bonk.</p>`;
这里的标签是单个标识符 SaferHTML
,但标签也可以是属性,比如 SaferHTML.escape
,甚至可以是方法调用,比如 SaferHTML.escape({unicodeControlCharacters: false})
。(准确地说,任何 ES6 MemberExpression 或 CallExpression 都可以用作标签。)
我们看到,未标记的模板字符串是简单字符串连接的简写形式。标记模板是完全不同的东西的简写形式:函数调用。
上面的代码等同于
var message =
SaferHTML(<var>templateData</var>, bonk.sender);
其中 <var>templateData</var>
是模板所有字符串部分组成的不可变数组,由 JS 引擎为我们创建。这里数组将有两个元素,因为标记模板中有两个字符串部分,由替换隔开。所以 <var>templateData</var>
将类似于 <a href="https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze" target="_blank">Object.freeze</a>(["<p>", " has sent you a bonk.</p>"]
。
(实际上,<var>templateData</var>
上还存在一个属性。我们不会在本文中使用它,但我会为了完整性而提到它:<var>templateData</var>.raw
是另一个包含标记模板中所有字符串部分的数组,但这次是它们在源代码中看起来的样子——保留了诸如 \n
之类的转义序列,而不是被转换为换行符等等。标准标签 String.raw
使用这些原始字符串。)
这使得 SaferHTML
函数可以自由地以无数种可能的方式解释字符串和替换。
在继续阅读之前,也许你想试着弄清楚 SaferHTML
应该做什么,然后尝试自己实现它。毕竟,它只是一个函数。你可以在 Firefox 开发者控制台中测试你的工作。
以下是一个可能的答案(也可以 作为 gist 获取)。
function SaferHTML(templateData) {
var s = templateData[0];
for (var i = 1; i < arguments.length; i++) {
var arg = String(arguments[i]);
// Escape special characters in the substitution.
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
// Don't escape special characters in the template.
s += templateData[i];
}
return s;
}
使用这个定义,标记模板 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>
)向他们发送了 bonk,你的用户也是安全的。无论那是什么意思。
(顺便说一下,如果函数使用 arguments 对象 的方式让你觉得有点笨拙,下周来看看。ES6 中还有另一个新功能,我想你会喜欢的。)
一个例子不足以说明标记模板的灵活性。让我们回顾一下我们之前列出的模板字符串限制,看看你还能做些什么。
- 模板字符串不会自动转义特殊字符。但正如我们所见,使用标记模板,你可以使用标签自己解决这个问题。
事实上,你可以做得比这更好。
从安全角度来看,我的
SaferHTML
函数非常弱。HTML 中的不同位置需要以不同的方式转义不同的特殊字符;SaferHTML
并没有转义所有字符。但只要付出一些努力,你就可以编写一个更智能的SaferHTML
函数,它实际上会解析templateData
中字符串中的 HTML 部分,以便它知道哪些替换是在普通 HTML 中;哪些是在元素属性中,因此需要转义'
和"
;哪些是在 URL 查询字符串中,因此需要 URL 转义而不是 HTML 转义;等等。它可以对每个替换执行正确的转义。因为 HTML 解析速度很慢,所以这听起来是不是很牵强?幸运的是,当模板再次被计算时,标记模板的字符串部分不会改变。
SaferHTML
可以缓存所有解析结果,以加快以后的调用。(缓存可以是 WeakMap,这是我们将在以后的文章中讨论的另一个 ES6 功能。) - 模板字符串没有内置的国际化功能。但使用标签,我们可以添加它们。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)
表示amount
是货币金额,以加元计。这就是标记模板的意义所在。
- 模板字符串不能替代 Mustache 和 Nunjucks,部分原因是它们没有内置的循环或条件语句语法。但现在我们开始明白如何解决这个问题,对吧?如果 JS 没有提供该功能,就编写一个提供该功能的标签。
// Purely hypothetical template language based on // ES6 tagged templates. var libraryHtml = hashTemplate` <ul> #for book in ${myBooks} <li><i>#{book.title}</i> by #{book.author}</li> #end </ul> `;
灵活性不止于此。请注意,标签函数的参数不会自动转换为字符串。它们可以是任何东西。返回值也是如此。标记模板甚至不一定是字符串!你可以使用自定义标签来创建正则表达式、DOM 树、图像、表示整个异步过程的承诺、JS 数据结构、GL 着色器……
带标签的模板为库设计者提供了创建强大特定领域语言的机会。 这些语言可能看起来与 JS 完全不同,但仍然可以无缝地嵌入 JS 并与语言的其余部分智能交互。 坦率地说,我想不出任何其他语言中有什么东西能与之匹敌。 我不知道这个特性会把我们带到哪里。 可能性令人兴奋。
我什么时候可以开始使用它?
在服务器端,ES6 模板字符串目前在 io.js 中得到支持。
在浏览器中,Firefox 34 及更高版本支持模板字符串。 它们是由 Guptha Rajagopal 在去年夏天作为实习项目实现的。 模板字符串在 Chrome 41 及更高版本中也得到支持,但在 IE 或 Safari 中不支持。 目前,如果您想在网络上使用模板字符串,则需要使用 Babel 或 Traceur。 您也可以立即在 TypeScript 中使用它们!
等等,那 Markdown 呢?
嗯?
哦,……问得好。
(本节与 JavaScript 关系不大。如果您不使用 Markdown,可以跳过此部分。)
有了模板字符串,Markdown 和 JavaScript 现在都使用 `
字符来表示特殊含义。 事实上,在 Markdown 中,它是内联文本中间的 code
片段的定界符。
这就带来了一个小问题! 如果您在 Markdown 文档中写下以下内容
To display a message, write `alert(`hello world!`)`.
它将显示为以下内容
要显示消息,请写入 alert(
hello world!)
。
请注意,输出中没有反引号。 Markdown 将所有四个反引号解释为代码定界符,并用 HTML 标签替换了它们。
为了避免这种情况,我们转向 Markdown 从一开始就包含的一个鲜为人知的功能:您可以使用多个反引号作为代码定界符,例如
To display a message, write ``alert(`hello world!`)``.
This Gist 包含详细信息,并且是用 Markdown 写成的,因此您可以查看源代码。
接下来
下周,我们将看看程序员在其他语言中享受了几十年的两个特性:一个是对于喜欢尽可能避免争论的人,另一个是对于喜欢争论的人。 我说的是函数参数,当然。 这两个特性实际上是为我们所有人准备的。
我们将通过在 Firefox 中实现这些特性的开发人员的视角来了解这些特性。 所以请在下周加入我们,届时客座作者 Benjamin Peterson 将深入介绍 ES6 默认参数和剩余参数。
11 条评论