详解JavaScript ES6中的模板字符串

在 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>
`);

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

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

不会自动转义特殊字符,为了避免跨站脚本漏洞,你还是需要小心对待不可信的数据,这一点上与普通字符串一样。
    不能与国际化库配合使用,不处理特殊语言格式的数字、日期等。
    不是模板引擎(比如 MustacheNunjucks)的替代品。模板字符串没有处理循环的语法 — 不能通过一个数组构建出一个表格(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 中,并且可以与语言的其余部分进行交互。顺便说一下,我还没有在其他语言中见过类似的特性,我不知道这个特性讲给我们带来些什么,但各种可能性还是非常令人兴奋的。

(0)

相关推荐

  • 深入浅析ES6 Class 中的 super 关键字

    以下只是个人的学习笔记: super这个关键字,既可以当作函数使用,也可以当作对象使用.在这两种情况下,它的用法完全不同. 1.当作函数使用 class A {} class B extends A { constructor() { super(); //ES6 要求,子类的构造函数必须执行一次super函数. } } 注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.construc

  • ES6入门教程之Class和Module详解

    本文主要介绍了ES6中Class和Module的相关内容,分享出来供大家参考学习,下面来看看详细的介绍: 一.Class ES6引入了Class(类)这个概念,作为对象的模板.通过class关键字,可以定义类. // 定义类 class Point() { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new

  • ES6新特性之类(Class)和继承(Extends)相关概念与用法分析

    本文实例讲述了ES6新特性之类(Class)和继承(Extends)相关概念与用法.分享给大家供大家参考,具体如下: 一.类(Class) 1.基本语法 JavaScript语言的传统方法是通过构造函数,定义并生成新对象.下面是一个例子 function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'

  • JavaScript ES6中CLASS的使用详解

    前言 对于javascript来说,类是一种可选(而不是必须)的设计模式,而且在JavaScript这样的[[Prototype]] 语言中实现类是很蹩脚的. 这种蹩脚的感觉不只是来源于语法,虽然语法是很重要的原因.js里面有许多语法的缺点:繁琐杂乱的.prototype 引用.试图调用原型链上层同名函数时的显式伪多态以及不可靠.不美观而且容易被误解成"构造函数"的.constructor. 除此之外,类设计其实还存在更进一步的问题.传统面向类的语言中父类和子类.子类和实例之间其实是复

  • 深入理解React中es6创建组件this的方法

    首发于:https://mingjiezhang.github.io/. 在JavaScript中,this对象是运行时基于函数的执行环境(也就是上下文)绑定的. 从react中的demo说起 Facebook最近一次更新react时,将es6中的class加入了组件的创建方式当中.Facebook也推荐组件创建使用通过定义一个继承自 React.Component 的class来定义一个组件类.官方的demo: class LikeButton extends React.Component

  • JavaScript学习笔记之ES6数组方法

    ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐,我们已经进入中国梦版本了).上一次标准的制订还是2009年出台的ES5.目前ES6的标准化工作正在进行中,预计会在14年12月份放出正式敲定的版本.但大部分标准已经就绪,且各浏览器对ES6的支持也正在实现中. ES6给数组添加了一些新特性,而这些新特性到目前为止完全可以运用到自己的业务层.在这一节中将总结有关于ES6给数组提供一些新特性的使用方法. ES6提供

  • ES6中class类用法实例浅析

    本文实例讲述了ES6中class类用法.分享给大家供大家参考,具体如下: 类语法是ES6中新增的一个亮点特色.我们熟悉的JavaScript终于迎来了真正意义上的类.在之前,想要通过javascript来实现类,通常会采用如下构造函数的模式: function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ['Shelby','Court']; } Person.pro

  • ES6中Class类的静态方法实例小结

    本文实例讲述了ES6中Class类的静态方法.分享给大家供大家参考,具体如下: 以前看过的es6的东西,又忘了,再总结下: 类相当于实例的原型,所有在类中定义的方法,都会被实例继承.如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法" class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new

  • JavaScript ES6的新特性使用新方法定义Class

    ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐,我们已经进入中国梦版本了).上一次标准的制订还是2009年出台的ES5.目前ES6的标准化工作正在进行中,预计会在14年12月份放出正式敲定的版本.但大部分标准已经就绪,且各浏览器对ES6的支持也正在实现中. ES6中定义类的方式, 就是ES3和ES5中定义类的语法糖,虽然也有些区别,但是整体定义类的方式更加简洁,类的继承更加方便, 如果想对ES6中的继承更加

  • 详解JavaScript ES6中的模板字符串

    在 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 的值

  • 详解JavaScript ES6中的Generator

    今天讨论的新特性让我非常兴奋,因为这个特性是 ES6 中最神奇的特性. 这里的"神奇"意味着什么呢?对于初学者来说,该特性与以往的 JS 完全不同,甚至有些晦涩难懂.从某种意义上说,它完全改变了这门语言的通常行为,这不是"神奇"是什么呢. 不仅如此,该特性还可以简化程序代码,将复杂的"回调堆栈"改成直线执行的形式. 我是不是铺垫的太多了?下面开始深入介绍,你自己去判断吧. 简介 什么是 Generator? 看下面代码: function* qu

  • 详解Javascript ES6中的箭头函数(Arrow Functions)

    ES6可以使用"箭头"(=>)定义函数,注意是函数,不要使用这种方式定义类(构造器). 一.语法 1. 具有一个参数的简单函数 var single = a => a single('hello, world') // 'hello, world' 2. 没有参数的需要用在箭头前加上小括号 var log = () => { alert('no param') } 3. 多个参数需要用到小括号,参数间逗号间隔,例如两个数字相加 var add = (a, b) =&g

  • 详解Javascript实践中的命令模式

    定义 Encapsulate a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests,and support undoable operations." 「命令模式」将「请求」封装成对象,以便使用不同的请求.队列或者日志来参数化其他对象,同时支持可撤消的操作. 这里的「请求」的定义,并不是我们前端常说的「Ajax 请求

  • 详解JavaScript编程中正则表达式的使用

    RegExp:是正则表达式(regular expression)的简写. 什么是 RegExp? 正则表达式描述了字符的模式对象. 当您检索某个文本时,可以使用一种模式来描述要检索的内容.RegExp 就是这种模式. 简单的模式可以是一个单独的字符. 更复杂的模式包括了更多的字符,并可用于解析.格式检查.替换等等. 您可以规定字符串中的检索位置,以及要检索的字符类型,等等. 语法 var patt=new RegExp(pattern,modifiers); 或 var patt=/patte

  • 详解JavaScript编程中的数组结构

    数组对象的作用是:使用单独的变量名来存储一系列的值. 创建数组, 为其赋值: 实例 var mycars = new Array(); mycars[0] = "Saab"; mycars[1] = "Volvo"; mycars[2] = "BMW"; 什么是数组? 数组对象是使用单独的变量名来存储一系列的值. 如果你有一组数据(例如:车名字),存在单独变量如下所示: var car1="Saab"; var car2=&q

  • 详解JavaScript正则表达式中的global属性的使用

    global是正则表达式对象的只读布尔属性.它指定是否一个特定的正则表达式进行全局匹配.否则它使用"g"属性创建. 语法 RegExpObject.global 下面是参数的详细信息: NA 返回值: 如果"g"修改被设置返回"TRUE",否则返回"FALSE". 例子: <html> <head> <title>JavaScript RegExp global Property</t

  • 详解JavaScript编程中的window与window.screen对象

    Window 对象 所有浏览器都支持 window 对象.它表示浏览器窗口. 所有 JavaScript 全局对象.函数以及变量均自动成为 window 对象的成员. 全局变量是 window 对象的属性. 全局函数是 window 对象的方法. 甚至 HTML DOM 的 document 也是 window 对象的属性之一: window.document.getElementById("header"); 与此相同: document.getElementById("h

  • 详解JavaScript es6的新增数组方法

    目录 1. forEach() 2. arr.filter() 3. arr.every() 4. arr.map() 5. arr.some() 总结 1. forEach() 遍历数组,无return 即使有return,也不会返回任何值,并且会影响原来的数组 callback的参数 value --当前索引的值 index --索引 arr --原数组 let arr = ["a", "b", "c", 1, 2, 3]; arr.forE

  • javascript ES6中set集合、map集合使用方法详解与源码实例

    set与map理解 ES6中新增,set集合和map集合就是一种数据的存储结构(在ES6之前数据存储结构只有array,object),不同的场景使用不同的集合去存储数据 set集合 Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用. set集合语法: //创建一个set集合,传参为一个可迭代的对象 const s1 = new Set(iterable); API 名称 类型 简介 Set.add() 原型方法 添加数据 Set.has() 原型方法 判断是否存在一个数据 S

随机推荐