[关闭]
@dlutwuwei 2016-01-07T18:38:03.000000Z 字数 2657 阅读 991

用javascript写一个高速的前端模板引擎

js


前端模板引擎出现在很早以前,最早接触它是在jquery插件里面,能省不少代码进行html的生成,for循环加字符串拼毕竟太丑陋,当时并没有感觉这个东西有啥大的作用。

前端模板引擎和后台的jsp,asp实现的原理差不多,最终都是生成一个html格式的字符串,只不多后台用的是java,前端用的是js,后台的java可以在jvm的环境里做所有服务器能做的事情,读取数据库等等,而前端js只能通过ajax和jsonp获取数据。区别在于后台模板引擎生成的内容只能一次性全部生成发送给浏览器,而前端模板可以通过接口分次加载,可以很灵活的减少首屏的内容大小,从而加快首屏展示速度。

在脱离组件化思想的情况下用模板引擎主要的作用是前后端分离开发,加快首屏展示的速度,但是庞大的模板文件和调试的困难让人头痛,而且性能上也不近人如意,后台对象序列化和前端字符串拼接消耗的性能还是比较可观的。但是如果组件化和mvc框架给前端开发带来的好处已经胜过这种损耗,前端模板引擎在其中扮演者比较重要的作用,如何实现一个前端模板引擎也许是很多前端新手非常希望了解的,下面就从零开始,从头实现一前端模板引擎,现在都讲究极致,那就谈谈如何加快模板引擎生成html的速度。

咱们取个名字,就叫stpl模板引擎,就用最经典的ejs语法定义模板吧

  1. <scirpt type='tpl' id='tpl'>
  2. <ul>
  3. <% for(var i=0; i < list.length; i++){%>
  4. <li><%=list[i]%></li>
  5. <% } %>
  6. </ul>
  7. </script>

大概是这样使用的

  1. var data = ['template','mastash','artTemplte']
  2. var tpl = document.getElementById('tpl').innerHtml;
  3. var html = stpl(tpl, data);
  4. document.body.innerHtml = html;

在chrome的V8引擎下,字符串拼接的速度非常快
下面实现我们的模板引擎,主要用的的js的api就是new Function([参数名称],[函数内容])

  1. function stpl(tpl, data) {
  2. var fn = function(d) {
  3. var i,
  4. k = [],
  5. v = [];
  6. for (i in d) {
  7. k.push(i);
  8. v.push(d[i]);
  9. }
  10. return (new Function(k, fn.$)).apply(d, v);
  11. };
  12. if (!fn.$) {
  13. fn.$ = 'var $=""\n';
  14. //先把特殊字符转义或者去处
  15. var tpls = tpl.replace(/[\r\t\n]/g, " ").replace(/\'/g, "\\\'").replace(/\s+/g, ' ').split('%>'),
  16. i = 0;
  17. //构建函数体
  18. while (i < tpls.length) {
  19. var p = tpls[i];
  20. var x = p.indexOf('<%');
  21. if (p[x + 2] == '=') {
  22. //<%=..%>
  23. fn.$ += "$+='" + p.substr(0, x) + "'\n";
  24. fn.$ += "$+=" + p.substr(x + 3) + "\n";
  25. } else if (x >= 0) {
  26. //<% %>
  27. fn.$ += "$+='" + p.substr(0, x) + "'\n";
  28. fn.$ += p.substr(x + 2) + "\n";
  29. } else {
  30. //html
  31. fn.$ += "$+='" + p + "'\n";
  32. }
  33. i++;
  34. }
  35. fn.$ += "return $";
  36. }
  37. return data ? fn(data) : fn;
  38. }
  39. module.exports = stpl;

嗯,上面的代码逻辑很清楚了,我们使用AMD封装

  1. var stpl = reqire('stpl.js')

这个引擎的速度已经比较快了,加速的主要方式是减小函数体里函数的计算量,我们只进行了一次切分和n次查找切分字符串,不传入数据可获取预编译的function,计算的复杂度

改进

还有一个加速的地方是,我们每次使用,都要重新new一个Function对象,这样很消耗性能,但是在调用前我们并不知道有什么参数,可以使用这样一个技巧

  1. <scirpt type='tpl' id='tpl'>
  2. <ul>
  3. <% for(var i=0; i < it.list.length; i++){%>
  4. <li><%=it.list[i]%></li>
  5. <% } %>
  6. </ul>
  7. </script>

我们固定一个参数it,模板所有参数都传给it,这样就是影响模板的写法,这样生成的funciton就可以重复使用了。

  1. var render = stpl(tpl);
  2. var html = render({list:data}))

性能比较-Function重复生成

性能处于中等偏上的位置,总体来说还是ok的,因为测试用例是10000次渲染,我们可以消除这个影响。

修改fn方法:

  1. var fn = function(d) {
  2. // 输入参数固定位一个参数,名称为it
  3. return new Function(['it'], fn.$);
  4. };

模板新写法,所有输入的数据均传入it对象

  1. <script id="stpl" type="text/tmpl">
  2. <ul>
  3. <% for (i = 0, l = it.list.length; i < l; i ++) { %>
  4. <li>用户: <%=it.list[i].user%>/ 网站:<%=it.list[i].site%></li>
  5. <% } %>
  6. </ul>
  7. </script>

性能比较-Function复用

性能已经超过最快的模板引擎,咱们因为功能单一,所以能快那么一点点

测试脚本

  1. var testList = [
  2. {
  3. name: 'stpl',
  4. tester: function () {
  5. var source = document.getElementById('stpl').innerHTML;
  6. var fn = stpl(source);
  7. for (var i = 0; i < number; i ++) {
  8. fn(data);
  9. }
  10. }
  11. }
  12. ]

示例

总结

  1. 模板引擎现在总要分两种,string-based,和dom-based;
  2. dom-based,如angular和Vue的模板和string-based的区别主要是更新的时候他们直接修改dom而不是直接使用innterHtml替换,但是在初始的时候依然无法逃过字符串拼接html文本,其实前端模板引擎拼速度完全没有作用,但是如果使用nodejs进行后端渲染的时候这个意义就非常大的,所以对于这块的理解在nodejs开发中也是很有作用的
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注