ES6中的模板字符串,ES6模板字面量

是因为模板字符串为 JS,  如果想在字符串中包含反引号

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

多行字符串

  自javascript诞生起,开发者们就一直在寻找一种能创建多行字符串的方法。如果使用双引号或单引号,字符串一定要在同一行才行

【反斜杠】

  由于javascript长期以来一直存在一个语法bug,在换行之前的反斜线( \
)可以承接下一行的代码,于是可以利用这个bug来创建多行字符串

var message = "Multiline \
string";
console.log(message); // "Multiline string"

  message
字符串打印输出时不会有换行,因为反斜线被视为延续符号而不是新行的符号。为了在输出中显示换行,需要手动加入换行符

var message = "Multiline \n\
string";
// "Multiline 
// string"
console.log(message); 

  在所有主流的 JS 引擎中,此代码都会输出两行,但是该行为被认定为一个
bug ,并且许多开发者都建议应避免这么做

  在ES6之前,通常都依靠数组或字符串的拼接来创建多行字符串

var message = ["Multiline ","string"].join("\n");
let message = "Multiline \n" +"string";

  JS一直以来都不支持多行字符串,开发者的种种解决方法都不够完美

【反引号】

  ES6
的模板字面量使多行字符串更易创建,因为它不需要特殊的语法,只需在想要的位置直接换行即可,此处的换行会同步出现在结果中

let message = `Multiline
string`;
// "Multiline
// string"
console.log(message); 
console.log(message.length); // 16

  在反引号之内的所有空白符都是字符串的一部分,因此需要特别留意缩进

let message = `Multiline
                             string`;
// "Multiline
                            // string"
console.log(message); 
console.log(message.length); //24

  以上代码中,模板字面量第二行前面的所有空白符都被视为字符串自身的一部分

  如果一定要通过适当的缩进来对齐文本,可以考虑在多行模板字面量的第一行空置并在后面的几行缩进

let html = `
<div>
    <h1>Title</h1>
</div>`.trim();

  以上代码中,模板字面量的第一行没有任何文本,第二行才有内容。
HTML标签的缩进增强了可读性,之后再调用trim()方法移除了起始的空行

  当然,也可以在模板字面量中使用 \n 来指示换行的插入位置

let message = `Multiline\nstring`;
// "Multiline
// string" 
console.log(message); 
console.log(message.length); // 16

 

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

前面的话

  JS
的字符串相对其他语言来说功能总是有限的,事实上,ES5中一直缺乏许多特性,如多行字符串、字符串格式化、HTML转义等。ES6通过模板字面量的方式进行了填补,模板字面量试着跳出JS已有的字符串体系,通过一些全新的方法来解决类似的问题。本文将详细介绍ES6模板字面量

 

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

变量占位符

  模板字面量看上去仅仅是普通JS字符串的升级版,但二者之间真正的区别在于模板字面量的变量占位符。变量占位符允许将任何有效的JS表达式嵌入到模板字面量中,并将其结果输出为字符串的一部分

  变量占位符由起始的 ${ 与结束的 } 来界定,之间允许放入任意的 JS
表达式。最简单的变量占位符允许将本地变量直接嵌入到结果字符串中

let name = "Nicholas",
message = `Hello, ${name}.`;
console.log(message); // "Hello, Nicholas."

  占位符 ${name} 会访问本地变量
name ,并将其值插入到 message 字符串中。
message变量会立即保留该占位符的结果

  既然占位符是JS表达式,那么可替换的就不仅仅是简单的变量名。可以轻易嵌入运算符、函数调用等

let count = 10,
price = 0.25,
message = `${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message); // "10 items cost $2.50."

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

  模板字面量本身也是 JS
表达式,因此可以将模板字面量嵌入到另一个模板字面量内部

let name = "Nicholas",
    message = `Hello, ${
        `my name is ${ name }`
    }.`;
console.log(message); // "Hello, my name is Nicholas."

 

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

基本用法

  模板字面量是增强版的字符串,它用反引号(`)标识

let message = `Hello world!`;
console.log(message); // "Hello world!"
console.log(typeof message); // "string"
console.log(message.length); // 12

   以上代码中,使用模板字面量语法创建一个字符串,并赋值给message变量,这时变量的值与一个普通的字符串无异

  如果想在字符串中包含反引号,只需使用反斜杠( \ )转义即可

let message = `\`Hello\` world!`;
console.log(message); // "`Hello` world!"
console.log(typeof message); // "string"
console.log(message.length); // 14

 

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

raw()

  String.raw方法,往往用来充当模板字面量的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字面量

let message1 = `Multiline\nstring`,
message2 = String.raw`Multiline\nstring`;
console.log(message1); // "Multiline
// string"
console.log(message2); // "Multiline\\nstring"

String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"

String.raw`Hi\u000A!`;
// 'Hi\\u000A!'

  如果原字符串的斜杠已经转义,那么String.raw不会做任何处理

String.raw`Hi\\n`// "Hi\\n"

String.raw方法可以作为处理模板字面量的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。

String.raw方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组

String.raw({ raw: 'test' }, 0, 1, 2);// 't0e1s2t'

// 等同于
String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);

 

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.

标签模板

  模板字面量真正的威力来自于标签模板,每个模板标签都可以执行模板字面量上的转换并返回最终的字符串值。标签指的是在模板字面量第一个反引号’`’前方标注的字符串

let message = tag`Hello world`;

  在这个示例中, tag 就是应用到 `Hello world` 模板字面量上的模板标签

【定义标签】

  标签可以是一个函数,调用时传入加工过的模板字面量各部分数据,但必须结合每个部分来创建结果。第一个参数是一个数组,包含Javascript解释过后的字面量字符串,它之后的所有参数都是每一个占位符的解释值

  标签函数通常使用不定参数特性来定义占位符,从而简化数据处理的过程

function tag(literals, ...substitutions) {
  // 返回一个字符串
}

  为了进一步理解传递给tag函数的参数,查看以下代码

let count = 10,
price = 0.25,
message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;

  如果有一个名为passthru()的函数,那么作为一个模板字面量标签,它会接受3个参数首先是一个literals数组,包含以下元素

  1、第一个占位符前的空字符串(“”)

  2、第一、二个占位符之间的字符串(” items cost $”)

  3、第二个占位符后的字符串(“.”)

  下一个参数是变量count的解释值,传参为10,它也成为了substitutions数组里的第一个元素

  最后一个参数是(count*price).toFixed(2)的解释值,传参为2.50,它是substitutions数组里的第二个元素

  [注意]literals里的第一个元素是一个空字符串,这确保了literals[0]总是字符串的始端,就像literals[literals.length-1]总是字符串的结尾一样。substitutions的数量总比literals少一个,这也意味着表达式substitutions.
Iength === literals. Iength-1的结果总为true

var a = 5;
var b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

  通过这种模式,我们可以将literals和substitutions两个数组交织在一起重组结果字符串。先取出literals中的首个元素,再取出substitution中的首个元素,然后交替继续取出每一个元素,直到字符串拼接完成。于是可以通过从两个数组中交替取值的方式模拟模板字面量的默认行为

function passthru(literals, ...substitutions) {
    let result = "";
    // 仅使用 substitution 的元素数量来进行循环
    for (let i = 0; i < substitutions.length; i++) {
        result += literals[i];
        result += substitutions[i];
    }
    // 添加最后一个字面量
    result += literals[literals.length - 1];
    return result;
}
let count = 10,
price = 0.25,
message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message); // "10 items cost $2.50."

  这个示例定义了一个passthru标签,模拟模板字面量的默认行为,展示了一次转换过程。此处的小窍门是使用substitutions.length来为循环计数

【应用】

  “标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容

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

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, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}

  上面代码中,sender变量往往是用户提供的,经过SaferHTML函数处理,里面的特殊字符都会被转义

var sender = '<script>alert("abc")</script>'; // 恶意代码
var message = SaferHTML`<p>${sender} has sent you a message.</p>`;

console.log(message);// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p>

  标签模板的另一个应用,就是多语言转换(国际化处理)

i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
// "欢迎访问xxx,您是第xxxx位访问者!"

  模板字符串本身并不能取代模板引擎,因为没有条件判断和循环处理功能,但是通过标签函数,可以自己添加这些功能

// 下面的hashTemplate函数
// 是一个自定义的模板处理函数
var libraryHtml = hashTemplate`
  <ul>
    #for book in ${myBooks}
      <li><i>#{book.title}</i> by #{book.author}</li>
    #end
  </ul>
`;

 

有了上面的方法,即使使用一个恶意的用户名,用户也是安全的。

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

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

一个简单的例子并不足以说明标签模板的灵活性,让我们重温一下上面列举的模板字符串的限制,看看我们还可以做些什么。

上面代码中,${user.name} 和 ${action} 被称为模板占位符,JavaScript 将把
user.name和 action 的值分别插到对应的位置上,然后生成像这样 “User
jorendorff is not authorized to do hockey.” 的字符串。

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

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

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

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

所以,上面代码等价于:

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

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

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

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

灵活性还不止于此,需要注意的是,标签函数的参数不会自动转换为字符串,参数可以是任何类型,返回值也一样。标签模板甚至可以不需要字符串,你可以使用自定义标签来创建正则表达式、DOM
树、图片、代表整个异步进程的 Promise、JS 数据结构、GL 着色器…

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

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

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

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

$("#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>
`);

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

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

您可能感兴趣的文章:

  • JavaScript学习笔记之ES6数组方法
  • JavaScript中ES6
    Babel正确安装过程
  • 跟我学习javascript的最新标准ES6
  • JavaScript
    ES6中CLASS的使用详解
  • 深入理解React中es6创建组件this的方法
  • 手把手教你搭建ES6的开发运行环境
  • ES6中非常实用的新特性介绍
  • 深入浅出讲解ES6的解构
  • 30分钟快速入门掌握ES6/ES2015的核心内容(上)

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

上面例子中的 name 和 amount 很好理解,将被 JS
引擎替换为对应的字符串,但是还有一个没有见过的占位符::c(CAD),这将被
i18n 标签处理,从 i18n 的文档可知::c(CAD)表示 amount
是加拿大美元货币值。

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

   
模板字符串不会自动转义特殊字符,但是我们可以通过标签模板来解决这个问题,事实上我们还可以将
SaferHTML 这个方法写的更好。从安全角度来看,这个 SaferHTML 非常脆弱。在
HTML 中,不同的地方需要用不同的方式去转义,SaferHTML
并没有做到。稍加思考,我们就可以实现一个更加灵活的 SaferHTML方法,能够将
templateData 中的任何一个 HTML 转义,知道哪个占位符是纯
HTML;哪个是元素的属性,从而需要对 ‘ 和 ” 转义;哪个是 URL 的 query
字符串,从而需要用 URL 的 escaping 方法,而不是 HTML 的
escaping;等等。这似乎有些牵强,因为 HTML
转义效率比较低。辛运是的,标签模板的字符串是保持不变的,SaferHTML
可以缓存已经转义过的字符串,从而提高效率。
   
模板字符串并没有内置的国际化特性,但通过标签模板,我们可以添加该特性。Jack
Hsu
的文章详细介绍了实现过程,看下面例子:

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

下面是一种实现(gist):

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}.`);
 }
}
context.fillText(`Ceci n'est pas une cha?ne.`, x, y);

context.fillText(`Ceci n'est pas une cha?ne.`, x, y);
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;
}