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

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

    1. let b = 10;
    2. tag`Hello ${ a + b } world ${ a * b }`;
    3. // 等同于
    4. tag(['Hello ', ' world ', ''], 15, 50);

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

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

    1. function tag(stringArr, value1, value2){
    2. // ...
    3. }
    4. // 等同于
    5. function tag(stringArr, ...values){
    6. // ...
    7. }

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

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

    tag函数所有参数的实际值如下。

    • 第一个参数:['Hello ', ' world ', '']
    • 第二个参数: 15
    • 第三个参数:50
    1. tag(['Hello ', ' world ', ''], 15, 50)

    我们可以按照需要编写tag函数的代码。下面是tag函数的一种写法,以及运行结果。

    1. let a = 5;
    2. let b = 10;
    3. function tag(s, v1, v2) {
    4. console.log(s[0]);
    5. console.log(s[1]);
    6. console.log(s[2]);
    7. console.log(v1);
    8. console.log(v2);
    9. return "OK";
    10. }
    11. // " world "
    12. // ""
    13. // 15
    14. // 50
    15. // "OK"

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

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

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

    1. function passthru(literals, ...values) {
    2. let output = "";
    3. let index;
    4. for (index = 0; index < values.length; index++) {
    5. output += literals[index] + values[index];
    6. }
    7. output += literals[index]
    8. return output;
    9. }

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

    1. let message =
    2. SaferHTML`<p>${sender} has sent you a message.</p>`;
    3. function SaferHTML(templateData) {
    4. let s = templateData[0];
    5. for (let i = 1; i < arguments.length; i++) {
    6. let arg = String(arguments[i]);
    7. // Escape special characters in the substitution.
    8. s += arg.replace(/&/g, "&amp;")
    9. .replace(/</g, "&lt;")
    10. .replace(/>/g, "&gt;");
    11. // Don't escape special characters in the template.
    12. s += templateData[i];
    13. }
    14. }

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

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

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

    1. i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`

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

    1. jsx`
    2. <div>
    3. <input
    4. ref='input'
    5. onChange='${this.handleChange}'
    6. defaultValue='${this.state.value}' />
    7. ${this.state.value}
    8. </div>
    9. `

    上面的代码通过jsx函数,将一个 DOM 字符串转为 React 对象。你可以在 GitHub 找到jsx函数的。

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

    1. java`
    2. class HelloWorldApp {
    3. public static void main(String[] args) {
    4. System.out.println("Hello World!"); // Display the string.
    5. }
    6. }
    7. `
    8. HelloWorldApp.main();

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

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

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

    请看下面的例子。

    1. tag`First line\nSecond line`
    2. function tag(strings) {
    3. console.log(strings.raw[0]);
    4. // strings.raw[0] 为 "First line\\nSecond line"
    5. }

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