前端模板与渲染

1 页面级的渲染

  在刚有web的时候,前端与后端的交互,非常直白,浏览器端发出URL,后端返回一张拼好了的HTML串。浏览器对其进行渲染。html中可能会混有一些php(或者php中混有一些html)。在服务端将数据与模板进行拼装,生成要返回浏览器端的html串。
  这与我们现在做一个普通网页没什么区别。只不过现在,我们更常使用模板技术来解决前后端耦合的问题。
  前端使用模板引擎,在html中写一些标签,与数据与逻辑基本无关。后端在渲染的时候,解析这些标签,生成HTML串,如smarty。其实前端与后端的交互在服务端就已经有一次了。
  1. front.tpl

  2. <div>
  3.     {%$a%}
  4. </div>

  5. 后端:

  6. // 设置变量
  7. $smarty->assign('a', 'give data');

  8. // 展示模板
  9. $smarty->display("front.tpl");

  10. 到前端时是渲染好的html串:

  11. <div>
  12.     give data
  13. </div>
复制代码
  这种方式的特点是展示数据快,直接后端拼装好数据与模板,展现到用户面前。

  2 异步的请求与新增模板

  新的时代,由ajax引领。(Asynchronous Javascript And XML),这种技术的历史,我就不再赘述。ajax的用法也有多种。
  ajax接受各种类型的返回。包括XML/JSON/String等。前端发起ajax请求,后端直接将数据返回。
  但是,读者们有没有想过,ajax回来的数据是干嘛用的呢?相信大部分人使用ajax拿回的数据是用来展示的。前端得把ajax拿回来的数据与模板进行拼装。这就面临了一个问题,当你的模板非常“华丽”的时候(也就是模板代码比较多的时候)。我们在前端写的拼字符串的逻辑,会非常的复杂。
  也有的人图省事,直接就在ajax的返回值中,传输拼装好的html字符串。这样可以直接把ajax拿到的html字符串,填充到页面上。
  下面实例说明一下两种方式:

  2.1 ajax获取字符串直接渲染方式

  如图2.1.1所示:
  1. index.html

  2. <!DOCTYPE html>
  3. <html>
  4.     <head>
  5.         <meta charset="utf-8" />
  6.     </head>
  7.     <body>
  8.         <h1>下面是待填充区域:</h1>
  9.         <div class="blankPlace"></div>
  10.         <script>
  11.             var xhr = new XMLHttpRequest();
  12.             xhr.onreadystatechange = function () {
  13.                 if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
  14.                     document.querySelector('.blankPlace').innerHTML = xhr.responseText;
  15.                 }   
  16.             };  
  17.             xhr.open('GET', './a.html');
  18.             xhr.send(null);
  19.         </script>
  20.     </body>
  21. </html>

  22. ========================================================================
  23. a.html

  24. <h2>我是模板</h2>
  25. <div>这是请求回来的数据</div>
复制代码
我是模板

这是请求回来的数据

1.png 
  图2.1.1
  2.2 ajax获取数据,前端进行拼装的方式
  1. <!DOCTYPE html>
  2. <html>
  3.     <head>
  4.         <meta charset="utf-8" />
  5.     </head>
  6.     <body>
  7.         <h1>下面是待填充区域:</h1>
  8.         <div class="blankPlace"></div>
  9.         <script>
  10.             var xhr = new XMLHttpRequest();
  11.             xhr.onreadystatechange = function () {
  12.                 if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
  13.                     var res = JSON.parse(xhr.responseText);
  14.                     document.querySelector('.blankPlace').innerHTML = ''
  15.                         +'<h2>'+
  16.                            '我是模板'+
  17.                         '</h2>'+
  18.                         '<div>'+
  19.                            res.data+
  20.                         '</div>';
  21.                 }   
  22.             };  
  23.             xhr.open('GET', './b.json');
  24.             xhr.send(null);
  25.         </script>
  26.     </body>
  27. </html>
复制代码
2.png 
  图 2.2.1

  2.3 两种方式的权衡

  那么,如何权衡两种方式呢?
  笔者单从自己的思维考虑,得出以下结论。如果这种模板的拼装会发生多次。是一个非常频繁的行为,且模板基本一致,只是数据变动的话,最好是一开始采用客户端拼装的方法。因为,同样的模板没有必要被传到客户端好几次。这样,我们可以剩下传输同样模板的流量,请求更快。
  类似于新闻流这种网站比较适合这种方式,如今日头条,如图2.3.1所示:
3.png 
  图2.3.1

4.png 
  图2.3.2

  笔者在DOM上面打了断点后,找到了其拼装模板,确是在客户端所做。
  不过,这种做法也有问题,就是用户同步刷新的时候,需要等页面渲染完,再发一个请求,去请求第一屏的数据,才能开始渲染。这个过程相当于发了两次请求,等待的时候还是有所感知的,如图2.3.3所示。
5.png 
  图2.3.3

  所以这种方式也是有些不尽人意的地方的。经过查看,网易新闻的web版,今日头条的web版,天天快报的web版均是采用这种方式。
  第二种方式,同步的时候,就将一段渲染好的HTML,直接输出到页面,而在异步的时候,请求的也是这段HTML,直接将请求回的HTML往页面上一塞就完成了。这样就可以达到同步页面的时候,直接输出,用户就不会看到等待中的小菊花了。
  百度首页就采取了这种方式。新闻直出,无需等待如图2.3.4
6.png 
  图2.3.4

  但是每次请求新闻的时候,也会去请求HTML片段,如图2.3.5所示
8.png 
  图2.3.5

  这种方式虽然首屏较快,但是,还是有优化空间的。

  2.4 混合方式

  看过了上述两种方式,聪明的你肯定会想:如果前端的js里写一份模板,后端的html(jsp/asp/smarty)中也写一份模板呢?这样,同步的时候,直接用后端HTML(jsp/asp/smarty)中的模板。异步拉取数据的时候,每次使用js中的模板进行拼装 。同步也能保证首屏的速度,异步也能保证传输量的限制与速度。可是这样,也会面临问题,那就是,你的模板需要维护两份。如果那天产品和你说,我要改一下页面的结构。你不得不改动HTML的时候。js中与jsp/asp/smarty中的模板都需要同样的更改两次。

  2.5 前端的模板引擎

  如果说,后端可以将html的拼装转变为使用引擎的话,前端为什么不可以呢?这里我先给大家写一个非常简单的模板解析函数,效果如图2.5.1
  1. <!DOCTYPE html>
  2. <html>
  3.     <head>
  4.         <meta charset="utf-8" />
  5.     </head>
  6.     <body>
  7.         <div id="content1"></div>
  8.         <div id="content2"></div>
  9.         <script>
  10.             // 这是我们的模板,怎么样,比直接写html字符串拼装看起来清爽多了吧?抱歉,markdown不能并列吧+号写在前面,所以我就写在了后面
  11.             var template = ''
  12.             +'<div>'+
  13.                '{%=a%}'+
  14.                '{%if (a===1){%}'+
  15.                    '<span>'+
  16.                        'a是1'+
  17.                    '</span>'+
  18.                '{%}%}'+
  19.             '</div>';
  20.             // 能解析输出与if条件语句的函数
  21.             function TEMPLATEparser(template, variables) {
  22.                 // 语法替换
  23.                 var funcStr = template
  24.                 .replace(/\{\%\=(\w+)\%\}/, function (code, variable) {
  25.                     return '"; str += "' + variable + '"; str += "';
  26.                 })
  27.                 .replace(/\{\%(if.*?)\%\}(.*?)\{\%(\})\%\}/, function (code, judge, content, end) {
  28.                     return '";' + judge + 'str+="' + content + '";' + end + 'str += "';
  29.                 });
  30.                 // 返回拼装函数
  31.                 return new Function(variables, 'var str = ""; str += "' + funcStr + '";return str;');
  32.             }

  33.             // 实验使用模板引擎去解析并传入变量生成模板
  34.             var outHTML = TEMPLATEparser(template, ['a'])(1);
  35.             document.getElementById('content1').innerHTML = outHTML;
  36.             outHTML = TEMPLATEparser(template, ['a'])(2);
  37.             document.getElementById('content2').innerHTML = outHTML;
  38.         </script>
  39.     </body>
  40. </html>
复制代码
7.png 
  图2.5.1

  这样就制作了一个简单的前端模板,有兴趣的读着可以看看我写的smartyMonkey前端模板引擎:
   https://github.com/houyu01/sm...

  2.6 前后端同构

  刚刚说过了前端模板,后端模板,前端与后端都需要模板引擎。比如,我们的在后端的模板是这样写的:
  // 接下来是伪代码
  1. // 接下来是伪代码
  2. <!DOCTYPE html>
  3. <html>
  4.     <head>
  5.         <meta charset="utf-8" />
  6.     </head>
  7.     <body>
  8.         // 前端需要模板去渲染
  9.         <textarea id="temp">include('./template.html')</textarea>
  10.         <div id="content1">
  11.             // 后端渲染模板
  12.             include('./template.html');
  13.         </div>
  14.         <div id="content2"></div>
  15.         <script>
  16.             // 这是我们的模板,怎么样,比直接写html字符串拼装看起来清爽多了吧?
  17.             var template = document.getElementById('temp').value;
  18.             // 能解析输出与if条件语句的函数
  19.             function TEMPLATEparser(template, variables) {
  20.                 // 语法替换
  21.                 var funcStr = template
  22.                 .replace(/\{\%\=(\w+)\%\}/, function (code, variable) {
  23.                     return '"; str += "' + variable + '"; str += "';
  24.                 })
  25.                 .replace(/\{\%(if.*?)\%\}(.*?)\{\%(\})\%\}/, function (code, judge, content, end) {
  26.                     return '";' + judge + 'str+="' + content + '";' + end + 'str += "';
  27.                 });
  28.                 // 返回拼装函数
  29.                 return new Function(variables, 'var str = ""; str += "' + funcStr + '";return str;');
  30.             }

  31.             // 实验使用模板引擎去解析并传入变量生成模板
  32.             var outHTML = TEMPLATEparser(template, ['a'])(1);
  33.             document.getElementById('content1').innerHTML = outHTML;
  34.             outHTML = TEMPLATEparser(template, ['a'])(2);
  35.             document.getElementById('content2').innerHTML = outHTML;
  36.         </script>
  37.     </body>
  38. </html>

  39. ============================
  40. template.html

  41. <div>
  42.   {%=a%}
  43.   {%if (a===1){%}
  44.       <span>
  45.           a是1
  46.       </span>
  47.   {%}%} 
  48. </div>
复制代码
  前端解析模板的引擎的语法,与后端j解析模板引擎语法一致。这样就达到了一份HTML前后端一起使用的效果。一改俱改,一板两用。其实这样也不算极致的完美,因为聪明的读者会发现,在页面加载的时候,我们多传了一份模板给到前端,如果用户不触发重新渲染的话,可能我们传到前端的模板就算白传了,造成了浪费。聪明的读者们可以考虑一下,如何把这份也给省下去。

  3 模板的更新

  有的时候,我们需要整片DOM进行更新,比如:
  1. <div class="我需要被更新" data-att="我需要被更新">
  2.     <span>我需要被更新</span>
  3.     <div class="我需要被更新"></div>
  4. </div>
复制代码
  我需要被更新
  1. <script>
  2.     // 数据更新
  3.     $.ajax().done(function (data) {
  4.         $('#wrapper').class(data.xxx);
  5.         $('#wrapper').attr('data-attr', data.xxx);
  6.         $('#wrapper span').html(data.xxx);
  7.         $('#wrapper div').class(data.xxx);
  8.     });
  9. </script><span style="font-size: small; font-family: Tahoma, Arial, Helvetica, snas-serif; line-height: 1.8em;"> </span>
复制代码
  这些html中的节点,需要在某次行为之后,一起被更新。那么我们的js可能会变成这样:
  1. <script>
  2.     // 模板
  3.     var template = ''
  4.     +'<div class="{%=newclass%}" data-attr="{%=newattr%}">'+
  5.         '<span>{%=newcontent%}</span>'+
  6.         '<div class={%=newinnerclass%}></div>'+
  7.     '</div>';
  8.     

  9.     // 数据更新
  10.     $.ajax().done(function (data) {
  11.         // 每次数据更新,直接把模板全刷一遍
  12.         $('#wrapper')[0].outerHTMl = TEMPLATEparser(template)(data);
  13.     });
  14. </script>
复制代码
  这样的维护,成本极大,还不如直接把整个html重新刷新一遍。这就遇到了我们的js拼装模板了:

  但是,直接刷HTML的成本太高。这样浏览器不得不整颗html子树全部重新构建一下,这种方法的性能又不如上一种方法好。
  好在react给了我们一种新的思路,它用最少的开销帮我们处理模板的更新,却又不用我们维护更新时繁琐的步骤。

原文链接: https://segmentfault.com/a/1190000005916423

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值