歡迎你們收看聊一聊系列,這一套系列文章,能夠幫助前端工程師們瞭解前端的方方面面(不只僅是代碼):
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的同窗,請私信我,一塊兒開發力量更大~
接下來的一篇文章,我將會和讀者們一塊兒聊聊前端存儲那些事兒,不要走開,請關注我.....
聊一聊前端存儲那些事兒