详解JavaScriptES6中的模板字符串
在 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>
`);
模板字符串中所有的空格、换⾏和缩进,都将被原样输出到结果字符串中。
下⾯我们来看看模板字符串做不到的事情:
不会⾃动转义特殊字符,为了避免跨站脚本漏洞,你还是需要⼩⼼对待不可信的数据,这⼀点上与普通字符串⼀样。
不能与国际化库配合使⽤,不处理特殊语⾔格式的数字、⽇期等。
不是模板引擎(⽐如或)的替代品。模板字符串没有处理循环的语法 — 不能通过⼀个数组构建出⼀个表格(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 += place(/&/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]);
js在字符串中添加字符// Escape special characters in the substitution.
s += place(/&/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 可以缓存已经转义过的字符串,从⽽提⾼效率。
模板字符串并没有内置的国际化特性,但通过标签模板,我们可以添加该特性。详细介绍了实现过程,看下⾯例⼦:
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 中,并且可以与语⾔的其余部分进⾏交互。顺便说⼀下,我还没有在其他语⾔中见过类似的特性,我不知道这个特性讲给我们带来些什么,但各种可能性还是⾮常令⼈兴奋的。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。