模板的诞生是为了将显示与数据分离,模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的html代码。

模板技术并不是什么神秘技术,干的是拼接字符串的体力活。模板引擎就是利用正则表达式识别模板标识,并利用数据替换其中的标识符。比如:
数据是<code>{name: '木的树'}</code>,那么通过模板引擎解析后,我们希望得到<code>hello, 木的树</code>。模板的前半部分是普通字符串,后半部分是模板标识,我们需要将其中的标识符替换为表达式。模板的渲染过程如下:
上面我们演示是简单的字符串替换,但对于模板引擎来说,要做的事情更复杂些。通常需要以下几个步骤:
利用正则表达式分解出普通字符串和模板标识符,<code><%=%></code>的正则表达式为<code>/<%=\s*([^%>]+)\s*%>/g</code>.
将模板标识符转换成普通的语言表达式
生成待执行语句
将数据填入执行,生成最终的字符串
demo代码如下:
上述代码中有如下部分:
为了能够与数据一起执行生成字符串,我们需要将原始的模板字符串转换成一个函数对象。这个过程称为<code>模板编译</code>。模板编译使用了<code>new function()</code>, 这里通过它创建了一个函数对象,语法如下:
<code>function()</code>构造函数接受多个参数,最后一个参数作为函数体的内容,其之前的参数全部作为生成的新函数的参数。需要注意的是function的参数全部是字符串类型,函数体部分对于字符串跟函数表达式一定要区分清楚,初学者往往在对函数体字符串中的普通字符串和表达式的拼接上犯错。一定要将函数体字符串和内部字符串正确拼接,如:
或者对其中的字符换使用<code>\"</code>
模板编译过程中每次都要利用function重新生成一个函数,浪费cpu。为此我们可以将函数缓存起来,代码如下:
利用<code>with</code>我们可以不用把模板标识符转换成<code>obj.name</code>,只需要保持<code>name</code>标识符即可。
如果上面的obj变成<code>var obj = {name: "<script>alert(\"xss\")</script>"};</code>,那么最终生成的结果就会变成:
为此我们需要堵上这个漏洞,基本就是要将形成html标签的字符转换成安全的字符,这些字符通常是<code>&</code>, <code><</code>, <code>></code>, <code>"</code>, <code>'</code>。转换函数如下:
这样下来,模板引擎应该变成这样:
这时候我们得到如下结果:
功能稍微强大的模板引擎,都允许在模板中添加一部分逻辑来控制页面的最终渲染。如:
这里我们用<code><%%></code>代表逻辑代码<code><%=%></code>代表模板中需要替换的标识符。我们的模板代码变成了如下所示:
第一步,我们将模板中的逻辑表达式找出来,用的正则表达式是<code>/<%\s*([^=][^%>]*)\s*%>/g</code>
注意在拼接时,为了防止函数字符串中的字符串没有闭合对表达式造成影响,我们在<code>key</code>前后都加了<code>'</code>来<code>保证其中的字符串闭合</code>。
第二步, 对可能存在的html标签进行转义
同样需要<code>注意前后的字符串闭合</code>
第三步,像先前一样处理模板标识符
仍然要<code>注意其中的字符串闭合问题</code>。
模板引擎是一个系统的问题,复杂模板还支持<code>模板嵌套</code>,这里就不介绍了,希望此文能够抛砖引玉,让大火带来更好的干货!