详解JavaScript ES6中的模板字符串


Posted in Javascript onJuly 28, 2015

在 ES6 中引入了一种新的字符串字面量 — 模板字符串,除了使用反引号 (`) 表示,它们看上去和普通的字符串没有什么区别。在最简单的情况下,他们就是普通的字符串:

context.fillText(`Ceci n'est pas une cha?ne.`, x, y);
 
context.fillText(`Ceci n'est pas une cha?ne.`, x, y);

之所以被称为模板字符串,是因为模板字符串为 JS 引入了简单的字符串插值特性,也就是说,可以方便优雅地将 JS 的值插入到字符串中。

很多地方可以用到模板字符串,看下面这个不起眼的错误提示消息:

function authorize(user, action) {
 if (!user.hasPrivilege(action)) {
  throw new Error(
   `User ${user.name} is not authorized to do ${action}.`);
 }
}
 
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 的值分别插到对应的位置上,然后生成像这样 “User jorendorff is not authorized to do hockey.” 的字符串。

现在,我们看到了一个比 + 运算符更优雅的语法,下面是一些你期待的特性:

    模板占位符可以是任何 JavaScript 表达式,所以函数调用和四则运算等都是合法的。(甚至你还可以在一个模板字符串中嵌套另一个模板字符串。)
    如果一个值不是字符串,它将被转换为字符串。例如,如果 action 是一个对象,那么该对象的 .toString() 将被调用,来将其转换为字符串。
    如果你想在模板字符串中使用反引号,你需要使用反斜杠 \ 将其转义。
    同样地,如果想在模板字符串中输出 ${,也需要使用反斜杠将其转义:\${ 或 $\{。
    模板字符串可以跨越多行:

$("#warning").html(`
 <h1>Watch out!</h1>
 <p>Unauthorized hockeying can result in penalties
 of up to ${maxPenalty} minutes.</p>
`);
 
$("#warning").html(`
 <h1>Watch out!</h1>
 <p>Unauthorized hockeying can result in penalties
 of up to ${maxPenalty} minutes.</p>
`);

    模板字符串中所有的空格、换行和缩进,都将被原样输出到结果字符串中。

下面我们来看看模板字符串做不到的事情:

    不会自动转义特殊字符,为了避免跨站脚本漏洞,你还是需要小心对待不可信的数据,这一点上与普通字符串一样。
    不能与国际化库配合使用,不处理特殊语言格式的数字、日期等。
    不是模板引擎(比如 Mustache 或 Nunjucks)的替代品。模板字符串没有处理循环的语法 — 不能通过一个数组构建出一个表格(table)。

为了解决这些限制,ES6 为开发者和库设计者提供了另一种模板字符串 — 标签模板。

标签模板的语法很简单,只需要在开始的反引号前引入一个标签。看第一个例子:SaferHTML,我们要使用这个标签模板来解决上述的第一个限制:自动转义特殊字符。

需要注意的是,SaferHTML 方法并不是 ES6 标准库提供的,我们需要自己来实现:

var message =
 SaferHTML`<p>${bonk.sender} has sent you a bonk.</p>`;
 
var message =
 SaferHTML`<p>${bonk.sender} has sent you a bonk.</p>`;

这里的 SaferHTML 标签是单个标识符,标签也可以是属性,比如 SaferHTML.escape,甚至还可以是方法调用:SaferHTML.escape({unicodeControlCharacters: false})。准确地说,任何 ES6 的成员表达式或调用表达式都可以作为标签。

可以看出,模板字符串仅仅是字符串连接的语法糖,而标签模板确是一个完全不同的东西:函数调用。

所以,上面代码等价于:

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

其中 templateData 是一个不可变的字符串数组,由 JS 引擎基于源模板字符串生成,这里的数组含有两个元素,因为模板字符串被占位符分隔后含有两个字符串,因此,templateData 将是这样: Object.freeze(["<p>", " has sent you a bonk.</p>"]

(事实上,templateData 上还有另一个属性:templateData.raw,本文并深入不讨论该属性。该属性的值也是一个数组,包含了标签模板中所有的字符串部分,但字符串中包含了转义序列,看上去更像源代码中的字符串,比如 \n。ES6 的内置标签 String.raw 将使用这些字符串。)

这就使得 SaferHTML 方法可以随意解析这两个字符串,存在 N 中替换方式。

在继续阅读钱,你可能在苦苦思索如何实现 SaferHTML 方法。

下面是一种实现(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;
}
 
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 这个方法写的更好。从安全角度来看,这个 SaferHTML 非常脆弱。在 HTML 中,不同的地方需要用不同的方式去转义,SaferHTML 并没有做到。稍加思考,我们就可以实现一个更加灵活的 SaferHTML方法,能够将 templateData 中的任何一个 HTML 转义,知道哪个占位符是纯 HTML;哪个是元素的属性,从而需要对 ' 和 " 转义;哪个是 URL 的 query 字符串,从而需要用 URL 的 escaping 方法,而不是 HTML 的 escaping;等等。这似乎有些牵强,因为 HTML 转义效率比较低。辛运是的,标签模板的字符串是保持不变的,SaferHTML 可以缓存已经转义过的字符串,从而提高效率。
    模板字符串并没有内置的国际化特性,但通过标签模板,我们可以添加该特性。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.




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 很好理解,将被 JS 引擎替换为对应的字符串,但是还有一个没有见过的占位符::c(CAD),这将被 i18n 标签处理,从 i18n 的文档可知::c(CAD)表示 amount 是加拿大美元货币值。

    模板字符串不能替代 Mustache 和 Nunjucks 这类模板引擎,部分原因在于模板字符串不支持循环和条件语句。我们可以编写一个标签来实现这类功能:

// 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>
`;

 
// 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 树、图片、代表整个异步进程的 Promise、JS 数据结构、GL 着色器…

标签模板允许库设计者创建强大的领域特定语言。这些语言可能看上去并不像 JS,但他们可以无缝嵌入到 JS 中,并且可以与语言的其余部分进行交互。顺便说一下,我还没有在其他语言中见过类似的特性,我不知道这个特性讲给我们带来些什么,但各种可能性还是非常令人兴奋的。

Javascript 相关文章推荐
js中的string.format函数代码
Aug 11 Javascript
jquery图片放大镜功能的实例代码
Mar 26 Javascript
html的DOM中document对象forms集合用法实例
Jan 21 Javascript
javascript关于运动的各种问题经典总结
Apr 27 Javascript
javascript原生ajax写法分享
Apr 10 Javascript
vue.js+boostrap项目实践(案例详解)
Sep 21 Javascript
JavaScript+Html5实现按钮复制文字到剪切板功能(手机网页兼容)
Mar 30 Javascript
vue webpack打包优化操作技巧
Feb 22 Javascript
Vue仿支付宝支付功能
May 25 Javascript
详解vue组件中使用路由方法
Feb 12 Javascript
如何在wxml中直接写js代码(wxs)
Nov 14 Javascript
Vue实现base64编码图片间的切换功能
Dec 04 Javascript
javascript解决IE6下hover问题的方法
Jul 28 #Javascript
JavaScript如何自定义trim方法
Jul 28 #Javascript
详解JavaScript ES6中的Generator
Jul 28 #Javascript
深入解读JavaScript中的Iterator和for-of循环
Jul 28 #Javascript
JavaScript调用客户端Java程序的方法
Jul 27 #Javascript
详细解读JavaScript编程中的Promise使用
Jul 27 #Javascript
JavaScript中的Repaint和Reflow用法详解
Jul 27 #Javascript
You might like
PHP迭代器实现斐波纳契数列的函数
2013/11/12 PHP
Laravel框架中VerifyCsrfToken报错问题的解决
2017/08/30 PHP
通过jquery的$.getJSON做一个跨域ajax请求试验
2011/05/03 Javascript
js判断上传文件类型判断FileUpload文件类型代码
2014/05/20 Javascript
常用的JavaScript WEB操作方法分享
2015/02/28 Javascript
jQuery调用ajax请求的常见方法汇总
2015/03/24 Javascript
js通过iframe加载外部网页的实现代码
2015/04/05 Javascript
使用AngularJS来实现HTML页面嵌套的方法
2015/06/17 Javascript
JavaScript基于ajax编辑信息用法实例
2015/07/15 Javascript
jQuery简单创建节点的方法
2016/09/09 Javascript
jQuery的ajax中使用FormData实现页面无刷新上传功能
2017/01/16 Javascript
浅谈JS和jQuery的区别
2019/03/27 jQuery
node实现爬虫的几种简易方式
2019/08/22 Javascript
使用layer弹窗提交表单时判断表单是否输入为空的例子
2019/09/26 Javascript
vue 接口请求地址前缀本地开发和线上开发设置方式
2020/08/13 Javascript
在Python中处理日期和时间的基本知识点整理汇总
2015/05/22 Python
windows上安装Anaconda和python的教程详解
2017/03/28 Python
对python .txt文件读取及数据处理方法总结
2018/04/23 Python
python3 读取Excel表格中的数据
2018/10/16 Python
python 多线程将大文件分开下载后在合并的实例
2018/11/09 Python
Python实现简单石头剪刀布游戏
2021/01/20 Python
多版本python的pip 升级后, pip2 pip3 与python版本失配解决方法
2019/09/11 Python
Python(PyS60)实现简单语音整点报时
2019/11/18 Python
flask框架url与重定向操作实例详解
2020/01/25 Python
Python结合Window计划任务监测邮件的示例代码
2020/08/05 Python
Python函数递归调用实现原理实例解析
2020/08/11 Python
利用python汇总统计多张Excel
2020/09/22 Python
python把一个字符串切开的实例方法
2020/09/27 Python
python爬虫中抓取指数的实例讲解
2020/12/01 Python
html5 拖拽上传图片实例演示
2013/04/01 HTML / CSS
Carrs Silver官网:英国著名的银器品牌
2020/08/29 全球购物
写自荐信三大法宝
2014/01/24 职场文书
销售顾问岗位职责
2014/02/25 职场文书
群众路线教育实践活动心得体会
2014/03/07 职场文书
综合素质自我评价怎么写
2014/09/14 职场文书
公安局副政委班子个人对照检查材料
2014/10/04 职场文书