写点什么

深入浅出 ES6(四):模板字符串

  • 2015-07-03
  • 本文字数:4791 字

    阅读完需:约 16 分钟

编者按:ECMAScript 6 已经正式发布了,作为它最重要的方言,Javascript 也即将迎来语法上的重大变革,InfoQ 特开设“深入浅出ES6 ”专栏,来看一下ES6 将给我们带来哪些新内容。本专栏文章来自 Mozilla Web 开发者博客,由作者授权翻译并发布。

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

现在,就让我们从最简单的知识学起吧!

反撇号(`)基础知识

ES6 引入了一种新型的字符串字面量语法,我们称之为模板字符串(template strings)。除了使用反撇号字符 ` 代替普通字符串的引号 ’ 或 " 外,它们看起来与普通字符串并无二致。在最简单的情况下,它们与普通字符串的表现一致:

context.fillText(Ceci n’est pas une chaîne., x, y);但是我们并没有说:“原来只是被反撇号括起来的普通字符串啊”。模板字符串名之有理,它为 JavaScript 提供了简单的字符串插值功能,从此以后,你可以通过一种更加美观、更加方便的方式向字符串中插值了。

模板字符串的使用方式成千上万,但是最让我会心一暖的是将其应用于毫不起眼的错误消息提示:

复制代码
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
`用户 ${user.name} 未被授权执行 ${action} 操作。`);
}
}

在这个示例中,${user.name}和 ${action}被称为模板占位符,JavaScript 将把 user.name 和 action 的值插入到最终生成的字符串中,例如:用户 jorendorff 未被授权打冰球。(这是真的,我还没有获得冰球许可证。)

到目前为止,我们所了解到的仅仅是比 + 运算符更优雅的语法,下面是你可能期待的一些特性细节:

  • 模板占位符中的代码可以是任意 JavaScript 表达式,所以函数调用、算数运算等这些都可以作为占位符使用,你甚至可以在一个模板字符串中嵌套另一个,我称之为模板套构(template inception)。
  • 如果这两个值都不是字符串,可以按照常规将其转换为字符串。例如:如果 action 是一个对象,将会调用它的.toString() 方法将其转换为字符串值。
  • 如果你需要在模板字符串中书写反撇号,你必须使用反斜杠将其转义:\\``等价于""。
  • 同样地,如果你需要在模板字符串中引入字符 $ 和{。无论你要实现什么样的目标,你都需要用反斜杠转义每一个字符:\\$\\{

与普通字符串不同的是,模板字符串可以多行书写:

复制代码
$("#warning").html(`
<h1> 小心!>/h1>
<p> 未经授权打冰球可能受罚
将近 ${maxPenalty}分钟。</p>
`);

模板字符串中所有的空格、新行、缩进,都会原样输出在生成的字符串中。

好啦,我说过要让你们轻松掌握模板字符串,从现在起难度会加大,你可以到此为止,去喝一杯咖啡,慢慢消化之前的知识。真的,及时回头不是一件令人感到羞愧的事情。 Lopes Gonçalves 曾经向我们证明过,船只不会被海妖碾压,也不会从地球的边缘坠落下去,他最终跨越了赤道,但是他有继续探索整个南半球么?并没有,他回家了,吃了一顿丰盛的午餐,你一定不排斥这样的感觉。

反撇号的未来

当然,模板字符串也并非事事包揽:

  • 它们不会为你自动转义特殊字符,为了避免跨站脚本漏洞,你应当像拼接普通字符串时做的那样对非置信数据进行特殊处理。
  • 它们无法很好地与国际化库(可以帮助你面向不同用户提供不同的语言)相配合,模板字符串不会格式化特定语言的数字和日期,更别提同时使用不同语言的情况了。
  • 它们不能替代模板引擎的地位,例如: Mustache 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)或调用表达式(CallExpression)都可作为标签使用。

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

上面的代码等效于:

复制代码
var message =
SaferHTML(templateData, bonk.sender);

templateData 是一个不可变数组,存储着模板所有的字符串部分,由 JS 引擎为我们创建。因为占位符将标签模板分割为两个字符串的部分,所以这个数组内含两个元素,形如 Object.freeze (["

", " has sent you a bonk.

(事实上,templateData 中还有一个属性,在这篇文章中我们不会用到,但是它是标签模板不可分割的一环:templateData.raw,它同样是一个数组,存储着标签模板中所有的字符串部分,如果我们查看源码将会发现,在这里是使用形如\n 的转义序列分行,而在 templateData 中则为真正的新行,标准标签 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]);
// 转义占位符中的特殊字符。
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/</g, ">");
// 不转义模板中的特殊字符。
s += templateData[i];
}
return s;
}

通过这样的定义,标签模板 SaferHTML<p>${bonk.sender} 向你示好。</p> 可能扩展为字符串 “

ES6<3er 向你示好。

(顺便一提,如果你感觉上述代码中在函数内部使用参数对象的方式令你感到枯燥乏味,不妨期待下一篇大作,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+ 支持模板字符串。它们由去年夏天的实习生项目组里的 Guptha Rajagopal 实现。模板字符串同样在 Chrome 41+ 中得以支持,但是 IE 和 Safari 都不支持。到目前为止,如果你想要在 web 端使用模板字符串的功能,你将需要 Babel Traceur 协助你完成 ES6 到 ES5 的代码转译,你也可以在 TypeScript 中立即使用这一特性。

等等——那么 Markdown 呢?

嗯?

哦…这是个好问题。

(这一章节与 JavaScript 无关,如果你不使用 Markdown ,可以跳过这一章。)

对于模板字符串而言,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 有具体代码细节,它由 Markdown 写成,所以你可以直接查看源代码。

下回预告

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

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


感谢刘振涛对本文的策划,徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015-07-03 00:01140705
用户头像

发布了 63 篇内容, 共 135.1 次阅读, 收获喜欢 38 次。

关注

评论

发布
暂无评论
发现更多内容

SAP Marketing Cloud 功能概述(四)

汪子熙

云原生 SaaS SAP 6月月更 Marketing Cloud

效率提升 100%,YRCloudFile 让自动驾驶安全更有保障

焱融科技

人工智能 自动驾驶 大数据 数据存储

快手实时数仓保障体系研发实践

Apache Flink

大数据 flink 编程 流计算 实时计算

融云通信“三板斧”,“砍”到了银行的心坎上

融云 RongCloud

SAP Marketing Cloud 功能概述(三)

汪子熙

SaaS SAP 云应用 6月月更 Marketing Cloud

特尔携手微软发挥边云协同势能,推动AI规模化部署

科技之家

P2PDB 白皮书

Rock-李益

区块链 去中心化数据库

CentOS中实现基于Docker部署BI数据分析

葡萄城技术团队

Docker centos BI 分析工具

传统的IO存在什么问题?为什么引入零拷贝的?

源字节1号

虚拟化是什么意思?包含哪些技术?与私有云有什么区别?

行云管家

云计算 运维 虚拟化 IT运维

等保备案是等保测评吗?两者是什么关系?

行云管家

备案 等保 等级保护 等保备案 等级测评

Java—Servlet

武师叔

6月月更

初识云原生安全:云时代的最佳保障

SEAL安全

云原生安全

业务与技术双向结合构建银行数据安全管理体系

易观分析

银行数字化

左手代码,右手开源,开源路上的一份子

开源社

开源

NFT质押流动性挖矿系统开发技术

薇電13242772558

NFT 流动性挖矿

全上链哈希游戏dapp系统定制(方案设计)

开发微hkkf5566

实时渲染:实时、离线、云渲染、混合渲染的区别

Finovy Cloud

渲染 云渲染

钛星数安加入龙蜥社区,共同打造网络安全生态

OpenAnolis小助手

开源 龙蜥社区 合作 CLA 钛星数安

远程办公之:在家露营办公小工具| 社区征文

甜甜的白桃

初夏征文

科创人·味多美CIO胡博:数字化是不流血的革命,正确答案藏在业务的田间地头

科创人

Vue项目的创建和托管

Python研究所

6月月更

模块五

Geek_2ce415

如何在物联网低代码平台中进行任务管理?

AIRIOT

低代码 物联网

在 Django 中呈现 HTML 表单(GET 和 POST)

海拥(haiyong.site)

Python django 6月月更

红象云腾完成与龙蜥操作系统兼容适配,产品运行稳定

OpenAnolis小助手

开源 龙蜥社区 龙蜥操作系统 红象云腾 兼容适配

制造业项目MDM主数据项目实施心得

agileai

数据治理 数据集成 企业服务总线 项目实施 制造业项目

leetcode 139. Word Break 单词拆分(中等)

okokabcd

LeetCode 动态规划 算法与数据结构

大数据培训Spark 高频面试考点分享

@零度

spark 大数据开发

居家办公更要高效-自动化办公完美提升摸鱼时间 | 社区征文

迷彩

Python AI 6月月更 初夏征文 Python自动化办公

4个不可不知的“安全左移”的理由

SEAL安全

安全左移 shift left

深入浅出ES6(四):模板字符串_JavaScript_Jason Orendorff_InfoQ精选文章