❤ ES6:快来玩转模板字符串

1,320 阅读3分钟

我正在参加「掘金·启航计划」

❤ 快来了解一下ES6的模板字符串的妙用!

❤ 1、介绍

ES6引入了一种新型的字符串字面量语法,我们称之为模板字符串(template strings)。我们可以使用反撇号字符 ` 代替普通字符串的引号 ' 或 " ,在这种用法上,他们与一般的普通字符串的引号 ' 或 " 没有什么大的区别。但是它有一些更加便捷的应用法。

官方定义: 模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

❤ 2、认识使用

(1)基本使用

// 普通字符串
`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}?`

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

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

如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

$('#list').html(`

<ul>
  <li>first</li>
  <li>second</li>
</ul>

`);

上面代码中,所有模板字符串的空格和换行,都是被保留的; 要将把行首和行尾的换行、空格等去掉,可以使用trim方法; 比如

    标签前面会有一个换行,不想要这个换行,可以使用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));
    

    如果需要引用模板字符串本身,在需要时执行,可以写成函数。

    let func = (name) => `Hello ${name}!`;
    func('Jack') // "Hello Jack!"
    

    上面代码中,模板字符串写成了一个函数的返回值。执行这个函数,就相当于执行这个模板字符串了。

    (2)标签模板

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

        alert`hello`
        // 等同于
        alert(['hello'])
    

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

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

        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会接受到value1和value2两个参数。

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

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

    “标签模板”的一个重要应用,就是过滤 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位访问者!"
        
    

    除此之外,你甚至可以使用标签模板,在 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();
    

    (3)String对象的raw方法

    模板处理函数的第一个参数(模板字符串数组),还有一个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两个字符,而不是换行符。这是为了方便取得转义之前的原始模板而设计的。
    

    String.raw方法用来充当模板字符串的处理函数,返回一个除表达式和变量会被替换,其它都保持原样的字符串。

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

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