8. 模板字面量
- 8.1. 概述
- 8.2. 介绍
- 8.2.1. 模板字面量
- 8.2.2. 模板字面量中的转义
- 8.2.3. 模板字面量中的行终止符始终为 LF (
\n
)
- 8.2.4. 带标签的模板字面量
- 8.3. 使用带标签的模板字面量的示例
- 8.3.1. 原始字符串
- 8.3.2. Shell 命令
- 8.3.3. 字节字符串
- 8.3.4. HTTP 请求
- 8.3.5. 更强大的正则表达式
- 8.3.6. 查询语言
- 8.3.7. 通过带标签的模板实现 React JSX
- 8.3.8. Facebook GraphQL
- 8.3.9. 文本本地化 (L10N)
- 8.3.10. 通过无标签模板字面量进行文本模板化
- 8.3.11. 用于 HTML 模板化的标签函数
- 8.4. 实现标签函数
- 8.4.1. 模板字符串的数量与替换的数量
- 8.4.2. 带标签的模板字面量中的转义:已处理与原始
- 8.4.3. 示例:
String.raw
- 8.4.4. 示例:实现用于 HTML 模板化的标签函数
- 8.4.5. 示例:组装正则表达式
- 8.5. 常见问题解答:模板字面量和带标签的模板字面量
- 8.5.1. 模板字面量和带标签的模板字面量来自哪里?
- 8.5.2. 宏和带标签的模板字面量之间有什么区别?
- 8.5.3. 我可以从外部源加载模板字面量吗?
- 8.5.4. 为什么反引号是模板字面量的分隔符?
- 8.5.5. 模板字面量曾经被称为模板字符串吗?
8.1 概述
ES6 有两种新的字面量:模板字面量 和 带标签的模板字面量。这两种字面量名称相似,外观也相似,但它们却截然不同。因此,区分以下几点非常重要
- 模板字面量(代码):支持插值的字符串字面量
- 带标签的模板字面量(代码):函数调用
- Web 模板(数据):包含待填充空白的 HTML
模板字面量 是可以跨越多行并包含插值表达式(通过 ${···}
插入)的字符串字面量
带标签的模板字面量(简称:带标签的模板)是通过在模板字面量之前提及函数来创建的
带标签的模板是函数调用。在上一个示例中,调用方法 String.raw
来生成带标签的模板的结果。
8.2 介绍
字面量是生成值的语法结构。例如,字符串字面量(生成字符串)和正则表达式字面量(生成正则表达式对象)。ECMAScript 6 有两种新的字面量
-
模板字面量 是支持插值和多行的字符串字面量。
-
带标签的模板字面量(简称:带标签的模板):是通过模板字面量提供参数的函数调用。
重要的是要记住,模板字面量和带标签的模板的名称略有误导性。它们与 Web 开发中经常使用的模板无关:模板是包含可以通过(例如)JSON 数据填充的空白的文本文件。
8.2.1 模板字面量
模板字面量是一种新型的字符串字面量,它可以跨越多行并插入表达式(包含其结果)。例如
字面量本身由反引号 (`
) 分隔,字面量内的插值表达式由 ${
和 }
分隔。模板字面量始终生成字符串。
8.2.2 模板字面量中的转义
反斜杠用于在模板字面量中进行转义。
它使您能够在模板字面量中提及反引号和 ${
除此之外,反斜杠的工作方式与字符串字面量相同
8.2.3 模板字面量中的行终止符始终为 LF (\n
)
终止行的常见方法是
- 换行符 (LF,
\n
, U+000A):由 Unix(包括当前的 macOS)使用
- 回车符 (CR,
\r
, U+000D):由旧的 Mac OS 使用。
- CRLF (
\r\n
):由 Windows 使用。
所有这些行终止符在模板字面量中都规范化为 LF。也就是说,以下代码在所有平台上都记录 true
8.2.4 带标签的模板字面量
以下是带标签的模板字面量(简称:带标签的模板)
在表达式后放置模板字面量会触发函数调用,类似于参数列表(括号中以逗号分隔的值)触发函数调用的方式。前面的代码等效于以下函数调用(实际上,第一个参数不仅仅是一个数组,但稍后会解释)。
因此,反引号中内容之前的名称是要调用的函数的名称,即标签函数。标签函数接收两种不同类型的数据
-
模板字符串,例如
'Hello '
。
-
替换,例如
firstName
(由 ${}
分隔)。替换可以是任何表达式。
模板字符串是静态已知的(在编译时),而替换仅在运行时才知道。标签函数可以随意处理其参数:它可以完全忽略模板字符串,返回任何类型的值,等等。
此外,标签函数会获取每个模板字符串的两个版本
- 一个“原始”版本,其中不解释反斜杠(
`\n`
变为 '\\n'
,一个长度为 2 的字符串)
- 一个“已处理”版本,其中反斜杠是特殊的(
`\n`
变为仅包含换行符的字符串)。
这允许 String.raw
(稍后解释)完成其工作
8.3 使用带标签的模板字面量的示例
带标签的模板字面量允许您轻松实现自定义嵌入式子语言(有时称为领域特定语言),因为 JavaScript 会为您完成大部分解析工作。您只需编写一个接收结果的函数。
让我们看一些例子。其中一些例子受到模板字面量的原始提案的启发,该提案通过其旧名称准字面量来指代它们。
8.3.1 原始字符串
ES6 包含用于原始字符串的标签函数 String.raw
,其中反斜杠没有特殊含义
每当您需要创建包含反斜杠的字符串时,这都很有用。例如
在 A 行中,String.raw
使我们能够像在正则表达式字面量中那样编写反斜杠。使用普通的字符串字面量,我们必须转义两次:首先,我们需要为正则表达式转义点。其次,我们需要为字符串字面量转义反斜杠。
8.3.2 Shell 命令
(来源:David Herman)
8.3.3 字节字符串
(来源:David Herman)
8.3.4 HTTP 请求
(来源:Luke Hoban)
8.3.5 更强大的正则表达式
Steven Levithan 举了一个例子,说明如何将带标签的模板字面量用于他的正则表达式库 XRegExp。
如果没有带标签的模板,则需要编写如下代码
我们可以看到 XRegExp 为我们提供了命名组(year
、month
、title
)和 x
标志。使用该标志,大多数空格将被忽略,并且可以插入注释。
字符串字面量在这里不能很好地工作有两个原因。首先,我们必须将每个正则表达式反斜杠键入两次,以便为字符串字面量对其进行转义。其次,输入多行很麻烦。
除了添加字符串之外,如果使用反斜杠结束当前行,您还可以在下一行继续字符串字面量。但这仍然涉及很多视觉混乱,特别是因为您仍然需要在每行的末尾通过 \n
显式换行。
使用带标签的模板可以解决反斜杠和多行问题
此外,带标签的模板允许您通过 ${v}
插入值 v
。我希望正则表达式库能够转义字符串并逐字插入正则表达式。例如
这等效于
8.3.6 查询语言
示例
这是一个 DOM 查询,它查找所有 CSS 类为 className
且目标是具有给定域的 URL 的 <a>
标签。标签函数 $
确保参数被正确转义,从而使这种方法比手动字符串连接更安全。
8.3.7 通过带标签的模板实现 React JSX
Facebook React 是“用于构建用户界面的 JavaScript 库”。它具有可选的语言扩展 JSX,使您能够为用户界面构建虚拟 DOM 树。此扩展使您的代码更简洁,但它也是非标准的,并且会破坏与 JavaScript 生态系统其余部分的兼容性。
库 t7.js 提供了 JSX 的替代方案,并使用带有 t7
标签的模板
在“为什么不使用模板字面量?”中,React 团队解释了他们为什么选择不使用模板字面量。一个挑战是在带标签的模板中访问组件。例如,在前面的示例中,从第二个带标签的模板访问 MyWidget
。一种冗长的做法是
相反,t7.js 使用通过 t7.assign()
填充的注册表。这需要额外的配置,但模板字面量看起来更好;尤其是在同时存在开始标签和结束标签的情况下。
8.3.8 Facebook GraphQL
Facebook Relay 是“用于构建数据驱动的 React 应用程序的 JavaScript 框架”。它的其中一部分是查询语言 GraphQL,其查询可以通过带有 Relay.QL
标签的模板创建。例如(摘自 Relay 主页)
从 A 行和 B 行开始的对象定义了片段,这些片段是通过返回查询的回调定义的。片段 tea
的结果放入 this.props.tea
中。片段 store
的结果放入 this.props.store
中。
这是查询操作的数据
此数据包装在一个 GraphQLSchema
实例中,并在其中获得名称 Store
(如 fragment on Store
中所述)。
8.3.9 文本本地化 (L10N)
本节介绍一种简单的文本本地化方法,它支持不同的语言和不同的地区(如何格式化数字、时间等)。假设有以下消息。
标签函数 msg
的工作原理如下。
首先,将字面量部分连接起来形成一个字符串,该字符串可用于在表中查找翻译。上一个示例的查找字符串是
例如,此查找字符串可以映射到德语翻译:
英语“翻译”将与查找字符串相同。
其次,使用查找结果来显示替换。由于查找结果包含索引,因此它可以重新排列替换的顺序。这已经在德语中完成,其中访问者编号位于站点名称之前。如何格式化替换可以通过注释来影响,例如 :d
。此注释表示应为 visitorNumber
使用特定于地区的十进制分隔符。因此,可能的英语结果是
在德语中,我们有如下结果
8.3.10 通过无标签模板字面量进行文本模板化
假设我们要创建 HTML,将以下数据显示在表格中
如前所述,模板字面量不是模板
- 模板字面量是立即执行的代码。
- 模板是包含可以用数据填充的孔的文本。
模板基本上是一个函数:输入数据,输出文本。该描述为我们提供了一个线索,说明如何将模板字面量转换为实际模板。让我们实现一个模板 tmpl
作为将数组 addrs
映射到字符串的函数
外部模板字面量提供括号 <table>
和 </table>
。在内部,我们嵌入了 JavaScript 代码,该代码通过连接字符串数组来生成字符串。该数组是通过将每个地址映射到两行表格来创建的。请注意,纯文本片段 <Jane>
和 <Croft>
未正确转义。下一节将解释如何通过标记模板执行此操作。
8.3.10.1 我应该在生产代码中使用这种技术吗?
对于较小的模板任务,这是一个有用的快速解决方案。对于较大的任务,您可能需要更强大的解决方案,例如模板引擎 Handlebars.js 或 React 中使用的 JSX 语法。
**致谢:**这种文本模板化方法基于 Claus Reinke 的一个想法。
8.3.11 用于 HTML 模板化的标签函数
与上一节中使用无标签模板进行 HTML 模板化相比,标记模板有两个优点
- 如果我们在
${}
前面加上感叹号,它们可以为我们转义字符。这是名称所必需的,其中包含需要转义的字符 (<Jane>
)。
- 它们可以自动为我们
join()
数组,因此我们不必自己调用该方法。
然后模板的代码如下所示。标签函数的名称是 html
请注意,Jane
和 Croft
周围的尖括号已转义,而 tr
和 td
周围的尖括号未转义。
如果在替换前面加上感叹号 (!${addr.first}
),则它将进行 HTML 转义。标签函数检查替换前的文本以确定是否进行转义。
html
的实现稍后显示。
8.4 实现标签函数
以下是一个标记模板字面量
此字面量(大致)触发以下函数调用
确切的函数调用看起来更像这样
标签函数接收两种输入
- 模板字符串(第一个参数):标记模板中不变的静态部分(例如
' lit2 '
)。模板对象存储模板字符串的两个版本
- 已处理:解释了
\n
等转义符。存储在 templateObject[0]
等中。
- 原始:未解释的转义符。存储在
templateObject.raw[0]
等中。
- 替换(剩余参数):通过
${}
嵌入模板字面量中的值(例如 subst1
)。替换是动态的,它们可以随着每次调用而改变。
全局模板对象背后的想法是,相同的标记模板可能会执行多次(例如,在循环或函数中)。模板对象使标签函数能够缓存来自先前调用的数据:它可以将其从输入类型 #1(模板字符串)派生的数据放入对象中,以避免重新计算它。缓存按_领域_发生(想想浏览器中的框架)。也就是说,每个调用站点和领域都有一个模板对象。
8.4.1 模板字符串的数量与替换的数量
让我们使用以下标签函数来探索有多少个模板字符串与替换相比。
模板字符串的数量始终比替换的数量多一个。换句话说:每个替换始终由两个模板字符串包围。
如果替换在字面量中排在第一位,则在其前面加上一个空的模板字符串
如果替换在字面量中排在最后一位,则在其后面加上一个空的模板字符串
空的模板字面量生成一个模板字符串,并且没有替换
8.4.2 标记模板字面量中的转义:已处理与原始
模板字符串有两种解释——已处理和原始。这些解释会影响转义
- 在已处理和原始解释中,
${
前面的反斜杠 (\
) 会阻止将其解释为替换的开头。
- 在已处理和原始解释中,反引号也通过反斜杠进行转义。
- 但是,每个反斜杠在原始解释中都会被提及,即使是那些转义替换和反引号的反斜杠。
标签函数 describe
允许我们探索这意味着什么。
让我们使用这个标签函数
如您所见,只要已处理的解释中有替换或反引号,则原始解释中也会有。但是,字面量中的所有反斜杠都会出现在原始解释中。
反斜杠的其他出现解释如下
- 在已处理模式下,反斜杠的处理方式与字符串字面量中的处理方式相同。
- 在原始模式下,反斜杠按字面使用。
例如
总而言之:反斜杠在原始模式下的唯一作用是转义替换和反引号。
8.4.3 示例:String.raw
以下是您如何实现 String.raw
8.4.4 示例:实现用于 HTML 模板化的标签函数
我之前演示了用于 HTML 模板化的标签函数 html
如果在替换前面加上感叹号 (!${addr.first}
),则它将进行 HTML 转义。标签函数检查替换前的文本以确定是否进行转义。
这是 html
的实现
模板字符串始终比替换多一个,这就是为什么我们需要在 A 行中追加最后一个模板字符串的原因。
以下是 htmlEscape()
的简单实现。
8.4.4.1 更多想法
使用这种模板化方法,您可以做更多的事情
8.4.5 示例:组装正则表达式
创建正则表达式实例有两种方法。
- 静态(在编译时),通过正则表达式字面量:
/^abc$/i
- 动态(在运行时),通过
RegExp
构造函数:new RegExp('^abc$', 'i')
如果您使用后者,那是因为您必须等到运行时才能获得所有必要的成分。您正在通过连接三种片段来创建正则表达式
- 静态文本
- 动态正则表达式
- 动态文本
对于 #3,特殊字符(点、方括号等)必须进行转义,而 #1 和 #2 可以按字面使用。正则表达式标签函数 regex
可以帮助完成此任务
regex
如下所示
8.5 常见问题解答:模板字面量和标记模板字面量
8.5.1 模板字面量和标记模板字面量来自哪里?
模板字面量和标记模板字面量是从 E 语言借用的,E 语言将此功能称为_准字面量_。
8.5.2 宏和标记模板字面量之间有什么区别?
宏允许您实现具有自定义语法的语言结构。为语法像 JavaScript 这样复杂的编程语言提供宏是很困难的。该领域的研究正在进行中(请参阅 Mozilla 的 sweet.js)。
虽然宏在实现子语言方面比标记模板强大得多,但它们依赖于语言的标记化。因此,标记模板是互补的,因为它们专门用于文本内容。
8.5.3 我可以从外部源加载模板字面量吗?
如果我想从外部源(例如文件)加载模板字面量(例如 `Hello ${name}!`
)怎么办?
如果你这样做,你就是在滥用模板字面量。鉴于模板字面量可以包含任意表达式并且是一个字面量,从其他地方加载它类似于加载表达式或字符串字面量——你必须使用 eval()
或类似的东西。
8.5.4 为什么反引号是模板字面量的分隔符?
反引号是 JavaScript 中为数不多的尚未使用的 ASCII 字符之一。语法 ${}
用于插值非常常见(Unix shell 等)。
8.5.5 模板字面量以前不是叫做模板字符串吗?
模板字面量这个术语在 ES6 规范制定过程中改的比较晚。以下是旧术语
- 模板字符串(字面量):模板字面量的旧称。
- 带标签的模板字符串(字面量):带标签的模板字面量的旧称。
- 模板处理器:标签函数的旧称。
- 字面量部分:模板字符串的旧称(替换一词保持不变)。