ES6语法总结(4)--字符串的扩展(2)

字符串的扩展

  1. 模板字符串javascript

  2. 实例:模板编译html

  3. 标签模板java

  4. String.raw()git

  5. 模板字符串的限制github

1.模板字符串

传统的 JavaScript 语言,输出模板一般是这样写的(下面使用了 jQuery 的方法)。web

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面这种写法至关繁琐不方便,ES6 引入了模板字符串解决这个问题。正则表达式

$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);

模板字符串(template string)是加强版的字符串,用反引号(`)标识。它能够看成普通字符串使用,也能够用来定义多行字符串,或者在字符串中嵌入变量。数组

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is not legal.`

console.log(`string text line 1 string text line 2`);

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

上面代码中的模板字符串,都是用反引号表示。若是在模板字符串中须要使用反引号,则前面要用反斜杠转义。app

let greeting = `\`Yo\` World!`;

若是使用模板字符串表示多行字符串,全部的空格和缩进都会被保留在输出之中。svg

$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `);

上面代码中,全部模板字符串的空格和换行,都是被保留的,好比<ul>标签前面会有一个换行。若是你不想要这个换行,可使用trim方法消除它。

$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());

模板字符串中嵌入变量,须要将变量名写在${}之中。

function authorize(user, action) {
  if (!user.hasPrivilege(action)) {
    throw new Error(
      // 传统写法为
      // 'User '
      // + user.name
      // + ' is not authorized to do '
      // + action
      // + '.'
      `User ${user.name} is not authorized to do ${action}.`);
  }
}

大括号内部能够放入任意的 JavaScript 表达式,能够进行运算,以及引用对象属性。

let x = 1;
let y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"

模板字符串之中还能调用函数。

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

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

若是大括号中的值不是字符串,将按照通常的规则转为字符串。好比,大括号中是一个对象,将默认调用对象的toString方法。

若是模板字符串中的变量没有声明,将报错。

// 变量place没有声明
let msg = `Hello, ${place}`;
// 报错

因为模板字符串的大括号内部,就是执行 JavaScript 代码,所以若是大括号内部是一个字符串,将会原样输出。

`Hello ${'World'}`
// "Hello World"

模板字符串甚至还能嵌套。

const tmpl = addrs => ` <table> ${addrs.map(addr => ` <tr><td>${addr.first}</td></tr> <tr><td>${addr.last}</td></tr> `).join('')}
  </table>
`;

上面代码中,模板字符串的变量之中,又嵌入了另外一个模板字符串,使用方法以下。

const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];

console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>

若是须要引用模板字符串自己,在须要时执行,能够像下面这样写。

// 写法一
let str = 'return ' + '`Hello ${name}!`';
let func = new Function('name', str);
func('Jack') // "Hello Jack!"

// 写法二
let str = '(name) => `Hello ${name}!`';
let func = eval.call(null, str);
func('Jack') // "Hello Jack!"

2.实例:模板编译

下面,咱们来看一个经过模板字符串,生成正式模板的实例。

let template = ` <ul> <% for(let i=0; i < data.supplies.length; i++) { %> <li><%= data.supplies[i] %></li> <% } %> </ul> `;

上面代码在模板字符串之中,放置了一个常规模板。该模板使用<%...%>放置 JavaScript 代码,使用<%= ... %>输出 JavaScript 表达式。

怎么编译这个模板字符串呢?

一种思路是将其转换为 JavaScript 表达式字符串。

echo('<ul>');
for(let i=0; i < data.supplies.length; i++) {
  echo('<li>');
  echo(data.supplies[i]);
  echo('</li>');
};
echo('</ul>');

这个转换使用正则表达式就好了。

let evalExpr = /<%=(.+?)%>/g;
let expr = /<%([\s\S]+?)%>/g;

template = template
  .replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
  .replace(expr, '`); \n $1 \n echo(`');

template = 'echo(`' + template + '`);';

而后,将template封装在一个函数里面返回,就能够了。

let script =
`(function parse(data){ let output = ""; function echo(html){ output += html; } ${ template } return output; })`;

return script;

将上面的内容拼装成一个模板编译函数compile

function compile(template){
  const evalExpr = /<%=(.+?)%>/g;
  const expr = /<%([\s\S]+?)%>/g;

  template = template
    .replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
    .replace(expr, '`); \n $1 \n echo(`');

  template = 'echo(`' + template + '`);';

  let script =
  `(function parse(data){ let output = ""; function echo(html){ output += html; } ${ template } return output; })`;

  return script;
}

compile函数的用法以下。

let parse = eval(compile(template));
div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] });
// <ul>
// <li>broom</li>
// <li>mop</li>
// <li>cleaner</li>
// </ul>

3.标签模板

模板字符串的功能,不只仅是上面这些。它能够紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。

alert`123`
// 等同于
alert(123)

标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

可是,若是模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。

let a = 5;
let b = 10;

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

上面代码中,模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。

函数tag依次会接收到多个参数。

function tag(stringArr, value1, value2){
  // ...
}

// 等同于

function tag(stringArr, ...values){
  // ...
}

tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。

tag函数的其余参数,都是模板字符串各个变量被替换后的值。因为本例中,模板字符串含有两个变量,所以tag会接受到value1value2两个参数。

tag函数全部参数的实际值以下。

  • 第一个参数:['Hello ', ' world ', '']
  • 第二个参数: 15
  • 第三个参数:50

也就是说,tag函数实际上如下面的形式调用。

tag(['Hello ', ' world ', ''], 15, 50)

咱们能够按照须要编写tag函数的代码。下面是tag函数的一种写法,以及运行结果。

let a = 5;
let b = 10;

function tag(s, v1, v2) {
  console.log(s[0]);
  console.log(s[1]);
  console.log(s[2]);
  console.log(v1);
  console.log(v2);

  return "OK";
}

tag`Hello ${ a + b } world ${ a * b}`;
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"

下面是一个更复杂的例子。

let total = 30;
let msg = passthru`The total is ${total} (${total*1.05} with tax)`;

function passthru(literals) {
  let result = '';
  let i = 0;

  while (i < literals.length) {
    result += literals[i++];
    if (i < arguments.length) {
      result += arguments[i];
    }
  }

  return result;
}

msg // "The total is 30 (31.5 with tax)"

上面这个例子展现了,如何将各个参数按照原来的位置拼合回去。

passthru函数采用 rest 参数的写法以下。

function passthru(literals, ...values) {
  let output = "";
  let index;
  for (index = 0; index < values.length; index++) {
    output += literals[index] + values[index];
  }

  output += literals[index]
  return output;
}

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

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

function SaferHTML(templateData) {
  let s = templateData[0];
  for (let i = 1; i < arguments.length; i++) {
    let 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函数处理,里面的特殊字符都会被转义。

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

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位访问者!"

模板字符串自己并不能取代 Mustache 之类的模板库,由于没有条件判断和循环处理功能,可是经过标签函数,你能够本身添加这些功能。

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

除此以外,你甚至可使用标签模板,在 JavaScript 语言之中嵌入其余语言。

jsx` <div> <input ref='input' onChange='${this.handleChange}' defaultValue='${this.state.value}' /> ${this.state.value} </div> `

上面的代码经过jsx函数,将一个 DOM 字符串转为 React 对象。你能够在 GitHub 找到jsx函数的具体实现

下面则是一个假想的例子,经过java函数,在 JavaScript 代码之中运行 Java 代码。

java` class HelloWorldApp { public static void main(String[] args) { System.out.println("Hello World!"); // Display the string. } } `
HelloWorldApp.main();

模板处理函数的第一个参数(模板字符串数组),还有一个raw属性。

console.log`123`
// ["123", raw: Array[1]]

上面代码中,console.log接受的参数,其实是一个数组。该数组有一个raw属性,保存的是转义后的原字符串。

请看下面的例子。

tag`First line\nSecond line`

function tag(strings) {
  console.log(strings.raw[0]);
  // strings.raw[0] 为 "First line\\nSecond line"
  // 打印输出 "First line\nSecond line"
}

上面代码中,tag函数的第一个参数strings,有一个raw属性,也指向一个数组。该数组的成员与strings数组彻底一致。好比,strings数组是["First line\nSecond line"],那么strings.raw数组就是["First line\\nSecond line"]。二者惟一的区别,就是字符串里面的斜杠都被转义了。好比,strings.raw 数组会将\n视为\\n两个字符,而不是换行符。这是为了方便取得转义以前的原始模板而设计的。

4.String.raw()

ES6 还为原生的 String 对象,提供了一个raw方法。

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

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

做为函数,String.raw的代码实现基本以下。

String.raw = function (strings, ...values) {
  let output = '';
  let index;
  for (index = 0; index < values.length; index++) {
    output += strings.raw[index] + values[index];
  }

  output += strings.raw[index]
  return output;
}

5.模板字符串的限制

前面提到标签模板里面,能够内嵌其余语言。可是,模板字符串默认会将字符串转义,致使没法嵌入其余语言。

举例来讲,标签模板里面能够嵌入 LaTEX 语言。

function latex(strings) {
  // ...
}

let document = latex` \newcommand{\fun}{\textbf{Fun!}} // 正常工做 \newcommand{\unicode}{\textbf{Unicode!}} // 报错 \newcommand{\xerxes}{\textbf{King!}} // 报错 Breve over the h goes \u{h}ere // 报错 `

上面代码中,变量document内嵌的模板字符串,对于 LaTEX 语言来讲彻底是合法的,可是 JavaScript 引擎会报错。缘由就在于字符串的转义。

模板字符串会将\u00FF\u{42}看成 Unicode 字符进行转义,因此\unicode解析时报错;而\x56会被看成十六进制字符串转义,因此\xerxes会报错。也就是说,\u\x在 LaTEX 里面有特殊含义,可是 JavaScript 将它们转义了。

为了解决这个问题,ES2018 放松了对标签模板里面的字符串转义的限制。若是遇到不合法的字符串转义,就返回undefined,而不是报错,而且从raw属性上面能够获得原始字符串。

function tag(strs) {
  strs[0] === undefined
  strs.raw[0] === "\\unicode and \\u{55}";
}
tag`\unicode and \u{55}`

上面代码中,模板字符串本来是应该报错的,可是因为放松了对字符串转义的限制,因此不报错了,JavaScript 引擎将第一个字符设置为undefined,可是raw属性依然能够获得原始字符串,所以tag函数仍是能够对原字符串进行处理。

注意,这种对字符串转义的放松,只在标签模板解析字符串时生效,不是标签模板的场合,依然会报错。

let bad = `bad escape sequence: \unicode`; // 报错
相关文章
相关标签/搜索