欢迎你们收看聊一聊系列,这一套系列文章,能够帮助前端工程师们了解前端的方方面面(不只仅是代码):
https://segmentfault.com/blog...php
做为现代应用,ajax的大量使用,使得前端工程师们平常的开发少不了拼装模板,渲染模板。咱们今天就来聊聊,拼装与渲染模板的那些事儿。html
若是喜欢本文请点击右侧的推荐哦,你的推荐会变为我继续更文的动力。前端
在刚有web的时候,前端与后端的交互,很是直白,浏览器端发出URL,后端返回一张拼好了的HTML串。浏览器对其进行渲染。html中可能会混有一些php(或者php中混有一些html)。在服务端将数据与模板进行拼装,生成要返回浏览器端的html串。react
这与咱们如今作一个普通网页没什么区别。只不过如今,咱们更常使用模板技术来解决先后端耦合的问题。git
前端使用模板引擎,在html中写一些标签,与数据与逻辑基本无关。后端在渲染的时候,解析这些标签,生成HTML串,如smarty。其实前端与后端的交互在服务端就已经有一次了。github
模板:web
front.tpl <div> {%$a%} </div> 后端: // 设置变量 $smarty->assign('a', 'give data'); // 展现模板 $smarty->display("front.tpl"); 到前端时是渲染好的html串: <div> give data </div>
这种方式的特色是展现数据快,直接后端拼装好数据与模板,展示到用户面前。ajax
新的时代,由ajax引领。(Asynchronous Javascript And XML),这种技术的历史,我就再也不赘述。ajax的用法也有多种。算法
ajax接受各类类型的返回。包括XML/JSON/String等。前端发起ajax请求,后端直接将数据返回。json
可是,读者们有没有想过,ajax回来的数据是干吗用的呢?相信大部分人使用ajax拿回的数据是用来展现的。前端得把ajax拿回来的数据与模板进行拼装。这就面临了一个问题,当你的模板很是“华丽”的时候(也就是模板代码比较多的时候)。咱们在前端写的拼字符串的逻辑,会很是的复杂。
也有的人图省事,直接就在ajax的返回值中,传输拼装好的html字符串。这样能够直接把ajax拿到的html字符串,填充到页面上。
下面实例说明一下两种方式:
如图2.1.1所示:
index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <h1>下面是待填充区域:</h1> <div class="blankPlace"></div> <script> var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) { document.querySelector('.blankPlace').innerHTML = xhr.responseText; } }; xhr.open('GET', './a.html'); xhr.send(null); </script> </body> </html> ======================================================================== a.html <h2>我是模板</h2> <div>这是请求回来的数据</div>
图2.1.1
效果如图2.2.1所示:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <h1>下面是待填充区域:</h1> <div class="blankPlace"></div> <script> var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) { var res = JSON.parse(xhr.responseText); document.querySelector('.blankPlace').innerHTML = '' +'<h2>'+ '我是模板'+ '</h2>'+ '<div>'+ res.data+ '</div>'; } }; xhr.open('GET', './b.json'); xhr.send(null); </script> </body> </html> ================================================ b.json {"data": "这是请求回来的数据"}
图 2.2.1
那么,如何权衡两种方式呢?
笔者单从本身的思惟考虑,得出如下结论。若是这种模板的拼装会发生屡次。是一个很是频繁的行为,且模板基本一致,只是数据变更的话,最好是一开始采用客户端拼装的方法。由于,一样的模板没有必要被传到客户端好几回。这样,咱们能够剩下传输一样模板的流量,请求更快。
相似于新闻流这种网站比较适合这种方式,现在日头条,如图2.3.1所示:
图2.3.1
图2.3.2
笔者在DOM上面打了断点后,找到了其拼装模板,确是在客户端所作。
不过,这种作法也有问题,就是用户同步刷新的时候,须要等页面渲染完,再发一个请求,去请求第一屏的数据,才能开始渲染。这个过程至关于发了两次请求,等待的时候仍是有所感知的,如图2.3.3所示。
图2.3.3
因此这种方式也是有些不尽人意的地方的。通过查看,网易新闻的web版,今日头条的web版,每天快报的web版均是采用这种方式。
第二种方式,同步的时候,就将一段渲染好的HTML,直接输出到页面,而在异步的时候,请求的也是这段HTML,直接将请求回的HTML往页面上一塞就完成了。这样就能够达到同步页面的时候,直接输出,用户就不会看到等待中的小菊花了。
百度首页就采起了这种方式。新闻直出,无需等待如图2.3.4
图2.3.4
可是每次请求新闻的时候,也会去请求HTML片断,如图2.3.5所示
图2.3.5
这种方式虽然首屏较快,可是,仍是有优化空间的。
看过了上述两种方式,聪明的你确定会想:若是前端的js里写一份模板,后端的html(jsp/asp/smarty)中也写一份模板呢?这样,同步的时候,直接用后端HTML(jsp/asp/smarty)中的模板。异步拉取数据的时候,每次使用js中的模板进行拼装 。同步也能保证首屏的速度,异步也能保证传输量的限制与速度。但是这样,也会面临问题,那就是,你的模板须要维护两份。若是那天产品和你说,我要改一下页面的结构。你不得不改动HTML的时候。js中与jsp/asp/smarty中的模板都须要一样的更改两次。
若是说,后端能够将html的拼装转变为使用引擎的话,前端为何不能够呢?这里我先给你们写一个很是简单的模板解析函数,效果如图2.5.1
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <div id="content1"></div> <div id="content2"></div> <script> // 这是咱们的模板,怎么样,比直接写html字符串拼装看起来清爽多了吧?抱歉,markdown不能并列吧+号写在前面,因此我就写在了后面 var template = '' +'<div>'+ '{%=a%}'+ '{%if (a===1){%}'+ '<span>'+ 'a是1'+ '</span>'+ '{%}%}'+ '</div>'; // 能解析输出与if条件语句的函数 function TEMPLATEparser(template, variables) { // 语法替换 var funcStr = template .replace(/\{\%\=(\w+)\%\}/, function (code, variable) { return '"; str += "' + variable + '"; str += "'; }) .replace(/\{\%(if.*?)\%\}(.*?)\{\%(\})\%\}/, function (code, judge, content, end) { return '";' + judge + 'str+="' + content + '";' + end + 'str += "'; }); // 返回拼装函数 return new Function(variables, 'var str = ""; str += "' + funcStr + '";return str;'); } // 实验使用模板引擎去解析并传入变量生成模板 var outHTML = TEMPLATEparser(template, ['a'])(1); document.getElementById('content1').innerHTML = outHTML; outHTML = TEMPLATEparser(template, ['a'])(2); document.getElementById('content2').innerHTML = outHTML; </script> </body> </html>
图2.5.1
这样就制做了一个简单的前端模板,有兴趣的读着能够看看我写的smartyMonkey前端模板引擎:
https://github.com/houyu01/sm...
刚刚说过了前端模板,后端模板,前端与后端都须要模板引擎。好比,咱们的在后端的模板是这样写的:
// 接下来是伪代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> // 前端须要模板去渲染 <textarea id="temp">include('./template.html')</textarea> <div id="content1"> // 后端渲染模板 include('./template.html'); </div> <div id="content2"></div> <script> // 这是咱们的模板,怎么样,比直接写html字符串拼装看起来清爽多了吧? var template = document.getElementById('temp').value; // 能解析输出与if条件语句的函数 function TEMPLATEparser(template, variables) { // 语法替换 var funcStr = template .replace(/\{\%\=(\w+)\%\}/, function (code, variable) { return '"; str += "' + variable + '"; str += "'; }) .replace(/\{\%(if.*?)\%\}(.*?)\{\%(\})\%\}/, function (code, judge, content, end) { return '";' + judge + 'str+="' + content + '";' + end + 'str += "'; }); // 返回拼装函数 return new Function(variables, 'var str = ""; str += "' + funcStr + '";return str;'); } // 实验使用模板引擎去解析并传入变量生成模板 var outHTML = TEMPLATEparser(template, ['a'])(1); document.getElementById('content1').innerHTML = outHTML; outHTML = TEMPLATEparser(template, ['a'])(2); document.getElementById('content2').innerHTML = outHTML; </script> </body> </html> ============================ template.html <div> {%=a%} {%if (a===1){%} <span> a是1 </span> {%}%} </div>
前端解析模板的引擎的语法,与后端j解析模板引擎语法一致。这样就达到了一份HTML先后端一块儿使用的效果。一改俱改,一板两用。其实这样也不算极致的完美,由于聪明的读者会发现,在页面加载的时候,咱们多传了一份模板给到前端,若是用户不触发从新渲染的话,可能咱们传到前端的模板就算白传了,形成了浪费。聪明的读者们能够考虑一下,如何把这份也给省下去。
有的时候,咱们须要整片DOM进行更新,好比:
<div class="我须要被更新" data-att="我须要被更新"> <span>我须要被更新</span> <div class="我须要被更新"></div> </div>
这些html中的节点,须要在某次行为以后,一块儿被更新。那么咱们的js可能会变成这样:
<script> // 数据更新 $.ajax().done(function (data) { $('#wrapper').class(data.xxx); $('#wrapper').attr('data-attr', data.xxx); $('#wrapper span').html(data.xxx); $('#wrapper div').class(data.xxx); }); </script>
这样的维护,成本极大,还不如直接把整个html从新刷新一遍。这就遇到了咱们的js拼装模板了:
<script> // 模板 var template = '' +'<div class="{%=newclass%}" data-attr="{%=newattr%}">'+ '<span>{%=newcontent%}</span>'+ '<div class={%=newinnerclass%}></div>'+ '</div>'; // 数据更新 $.ajax().done(function (data) { // 每次数据更新,直接把模板全刷一遍 $('#wrapper')[0].outerHTMl = TEMPLATEparser(template)(data); }); </script>
可是,直接刷HTML的成本过高。这样浏览器不得不整颗html子树所有从新构建一下,这种方法的性能又不如上一种方法好。
好在react给了咱们一种新的思路,它用最少的开销帮咱们处理模板的更新,却又不用咱们维护更新时繁琐的步骤。有兴趣的读者能够了解一下react-web的diff算法及其应用。
https://segmentfault.com/a/11...
好了,关于前端常见的模板的拼装与更新,咱们就讲到这里,同窗们有没有考虑过,本身的项目中,若是有异步请求并渲染的逻辑的时候,采用前端拿数据拼装、前端拿拼装好的模板、混合使用哪一种更好呢?
文中说起到的例子,均在github上能够找到:https://github.com/houyu01/te...
若是有想一块儿开发smartyMonkey的同窗,请私信我,一块儿开发力量更大~
接下来的一篇文章,我将会和读者们一块儿聊聊前端存储那些事儿,不要走开,请关注我.....
聊一聊前端存储那些事儿