我对模版字符串中标签模板的思考

模板字符串

模板字符串的几个特性:

  • 可以嵌入变量,或者表达式
  • 会保留多个空格、换行、缩进
//嵌入变量
var name = "Kyle"; 
var greeting = `Hello ${name}!`; 
console.log( greeting ); // "Hello Kyle!"


//多行
var text = 
`Now is the time for all good men 
to come to the aid of their 
country!`; 
console.log( text ); 
// Now is the time for all good men 
// to come to the aid of their 
// country!var text = 

//插入表达式
function upper(s) { 
    return s.toUpperCase(); 
} 
var who = "reader"; 
var text = 
`A very ${upper( "warm" )} welcome 
to all of you ${upper( `${who}s` )}!`; 
console.log( text ); 
// A very WARM welcome 
// to all of you READERS!

标签模板

模板字符串还可以跟在一个函数后面,该函数将被调用来处理这个模板字符串。这被称为"标签模板"功能。
alert`123`
// 等同于
alert(123)

如果模板字符串中含有变量,标签模板会进行特殊处理,它会根据嵌入的变量,把模板字符串拆开。
普通字符串组成数组做位第一个参数,插入的变量依次作为第二个、第三个...变量。

var a = 5;
var b = 10;

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

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

// 等同于

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

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

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

为什么要有标签模板?

我第一次看到标签模板的时候,不禁在想为什么要有标签模板,它有什么普通函数不能替代的地方呢?如果没有不可替代的地方,那为什么还要创造
它呢?
答案是:标签模板确实有它存在的意义,也有普通函数无法替代它的地方。

const logArgs = (...args) => console.log(...args)

先用普通函数形式调用

logArgs('a', 'b')
// -> a b

再用一个简单的模板字符串试试:

logArgs``
// -> ["", raw: Array(1)]

上面输出了一个空数组,然后传入一个有值的模板字符串:

logArgs`I like pizza`
// -> ["I like pizza"]

现在,进一步网模板字符串中传入变量


const favoriteFood = 'pizza'
//当作普通函数调用,直接出入拼接的字符串
logArgs(`I like ${favoriteFood}.`)
// -> I like pizza.

//使用标签模板,会输出处理后的参数
const favoriteFood = 'pizza'

logArgs`I like ${favoriteFood}.`
// -> ["I like ", "."] "pizza"

变换过程是这样的:

标签模板例子

当我们传入多个变量时,每一个插入的变量都作为下一个参数了

const favoriteFood = 'pizza'
const favoriteDrink = 'obi'

logArgs`I like ${favoriteFood} and ${favoriteDrink}.`
// -> ["I like ", " and ", "."] "pizza" "obi"

这样看来,标签模版有什么大的作用呢?


//普通函数
logArgs(`Test ${() => console.log('test')}`)
// -> Test () => console.log('test')

console.log(() => console.log('test'))
// () => console.log('test')

普通函数,遇到变量是一个函数是,会把函数变成字符串形式的,而没有别的处理,看看标签模板的特殊之处,模板字符串中嵌入变量

   logArgs`Test ${() => console.log('test')}`
   // -> ["Test", ""] () => console.log('test')

可以看出,标签模板把模板字符串中的变量解析了出来,函数还是一个函数不会变成字符串。我们有能力拿到函数,那么我们也可以执行这个函数

const execFuncArgs = (...args) => args.forEach(arg => {
  if (typeof arg === 'function') {
    arg()
  }
})

上面的函数,我们会忽略不是函数的参数,是函数就执行它

execFuncArgs('a', 'b')
// -> undefined

execFuncArgs(() => { console.log('this is a function') })
// -> "this is a function"

execFuncArgs('a', () => { console.log('another one') })
// -> "another one"

让我们把它当作普通函数来调用带有变量的模板字符串

execFuncArgs(`Hi, ${() => { console.log('Executed!') }}`)
// -> undefined

为什么会是undfined呢?因为,普通函数调用的方式,模板字符串会全部变成字符串的形式,变量也变成了字符串的形式
实际上调用参数是这样的: "Hi, () => { console.log('I got executed!') }".

把上面的函数当作"标签模板"来调用

execFuncArgs`Hi, ${() => { console.log('Executed!') }}`
// -> "Executed!"

与之前相反,execFuncArgs的第二个参数实际上是一个函数,然后会执行这个函数。
由此可以解决我的疑问了:
标签模板可以对传入的模板字符串中的变量,进行提取,并进一步处理,普通函数根本取不到这些变量,就甭提进一步处理了。

标签模板的实际应用

“标签模板”的一个重要应用,就是过滤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>`;

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

还有common-tags库中的oneLine标签函数

import {oneLine} from 'common-tags'

oneLine`
  foo
  bar
  baz
`
// "foo bar baz"

总之,标签模板功能很强大,可能一开始并不会觉得厉害之处,平时工作中也不会用到,但是这些知识是有用的,在很多库中会用到它,我们
使用这些库的时候也在不自觉中使用了标签模板,可以慢慢开始了解它,并使用它。

参考文章:
ES6 Tagged Template Literals
The magic behind ? styled-components

阅读 406

推荐阅读
justsso
用户专栏

学习与实践的记录者

0 人关注
8 篇文章
专栏主页