一、ES6 标签模板

我个人觉得ES6最好的几个特性之一就是 模板字符串,这玩意儿简直就是DOM拼接的福利啊,再也不用各种 += '之类的操作了。

刚接触ES6模板字符串的时候,有一个概念确实没弄清楚,就是标签模板,我只知道他有什么用,至于他是怎么操作的,如何用它来进行实际的使用等等都一概不知,或者是比较晕。

相信看过阮一峰老师的《ES6标准入门》,都知道有一个使用标签模板来过滤HTML字符串,防止用户恶意输入内容的示例。

起初我对这个示例真的是晕头转向,也是自己没有认真领会标签模板的所有的内容导致的,看的不仔细,也没有认真研究。

因此特意花时间,记一下。

1、 标签模板的使用

使用标签模板是很简单的,无非就在模板之前加一个标签即可。

比如下面的:将模板中的数字全部 *10 (假设都是数字---只是举例)

        const x = 1, y = 2;
        const resStr = `${x} + ${y} = ${x + y}`;
        console.log(resStr);// 1 + 2 = 3

本来上面会输出 1 + 2 = 3 ,但是现在我想输出 10 + 20 = 30,并且使用标签模板来实现。

则可以这么来加标签:

        const x = 1, y = 2;
        const resStr = strWork`${x} + ${y} = ${x + y}`;
        console.log(resStr);// 10 + 20 = 30
        // 标签处理
        function strWork(templateData) {
            let s = templateData[0];
            const args = arguments;
            for (i = 1, len = args.length; i < len; i++) {
                let arg = Number(args[i]);
                s += String(arg * 10);
                s += templateData[i];
            }
            return s;
        }

两处不同:

    1. const resStr = strWork ` ${x} + ${y} = ${x + y} \`
    • 在模板字符串前面加了一个标签为 strWork
    1. strWork 本质上是一个函数,传了一个参数,就是上面的模板字符串,这个函数就是对模板字符串的处理过程。可以看成是一个预处理。

2、 标签模板的原理

1)标签实际调用的过程

可以看到,在 strWork() 这个函数中用了 templateData 这个参数,还用了 arguments ,而 arguments 和
template 还不一样。问题的关键就是这个地方

实际上当在调用 strWork `xxxxxx` 的时候,本质上是调用的 strWork() 函数,但是调用的方式又很不一样:

上面的 strWork 函数只有一个参数 template, 实际上使用 strWork 的时候会触发 至少一个函数,也就是 strWork() 调用的时候其实是 strWork(templateData, ...args);

templateData 这个参数是一个数组,里面包含的内容是 模板字符串中,不会被替换的那些内容

  • 比如 `${x} + ${y} = ${x + y}` 这个模板字符串中,不会被替换的部分包括: + ,`
    = (注意包含空格) 这两部分,因此实际上 templateData = [' + ', ' = ']`;

而要在模板字符串中替换的部分如 x,y,x+y 是放在后面的参数中的,也就是 ...args 里面

  • 因此 strWork实际调用的是 strWork([' + ', ' = '], x, y, x+y)

2)标签处理的理论过程

上面知道了strWork 实际上是如何调用的,就可以很方便的传入的参数做处理。

但是标签在处理的过程中,是有一个固定的处理顺序的,我们都知道:

templateData 里面存放的是模板字符串中固定的内容,而后面的参数存放的是要代入的变量内容,这两部分分开的内容是通过顺序进行组合拼接的。

打个比方:两个数组,分别是 const arr1 = ['a', 'b', 'c']const arr2 = [1, 2, 3] ,现在按照下面的规格组合在一起:

arr1[0] + arr2[0] + arr1[1] + arr2[1] + arr1[2] + arr2[2];
// a1b2c3

结果很明显是 a1b2c3

实际上标签模板也是这么操作的,templateData 中存放的是不变的量, ...args (用来指代除了templateData的其他参数)中存放的是要代入的变量,同样适用上面的拼接方式,组合成最后的处理过的字符串。

实际上我们在使用标签模板的时候,处理的就是要插入的值,不变的值我们也不需要操作

因此 strWork 函数的目的就是 对 ...args 进行处理,同时拼接成新的字符串并放回

3) strWork 的处理过程阐述

上面的 strWork() 函数中,根据拼接规则,首先获取`templateData[0]的值,赋值给 s .

然后获取所有参数的变量,并循环进行处理。需要注意的是,循环是从1开始的,因为第一个参数是 templateData,不需要这个参数

获取当前循环到的参数,然后进行一些处理,处理完之后拼接到字符串 s 中。

随后需要将当前 templateData[i] 的内容也拼接上。 这就相当于在 templateData[i-1]templateData[i+1] 中间 插入了一个 arguments[i] (和上面 a1b2c3 的原理一样)

循环之后,返回处理过的 s 即可。

二、使用标签模板规避恶意脚本

const name = "<script>alert('xss');</script>";
document.write(name);

111.png

这应该是最浅显易懂的一个恶意脚本了,所谓的恶意脚本不仅仅是 <script> 这样的,有时候恶意的 html 标签也可能导致页面瘫痪掉。

因此过滤HTML标签是需要做的一个工作,而使用标签模板能够非常容易的解决这个问题。

下面的例子是各种说 标签模板 的地方都会出现的,我不确定第一次出现是不是在《ES6标准入门》中,但是用来表现标签模板的好处很恰当。

        // 模板标签过滤HTML字符串
        function safeHTML(templateData) {
            let s = templateData[0];
            const args = arguments;
            for (let i = 1, len = args.length; i < len; i++) {
                let arg = String(args[i]);
                // 特殊字符的替换
                s += arg.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
                s += templateData[i];
            }
            return s;
        }
        const str = '<p> <&&&&';
        console.log(safeHTML`hello world ${str}`);

最终输出的内容是 hello world <p> <&&&&

我第一次看上面的代码,是似懂非懂的,所以就认真的学习了一下标签模板,写了这篇文章,具体的原理上面已经说得非常清楚了。

而上面的规避也不是完全的,只是一个demo。