前端模板與渲染方式


1 頁面級的渲染


    再剛有web的時候,前端與后端的交互,非常直白,瀏覽器端發出URL,后端返回一張拼好了的HTML串。瀏覽器對其進行渲染。html中可能會混有一些php(或者php中混有一些html)。在服務端將數據與模板進行拼裝,生成要返回瀏覽器端的html串。

    這與我們現在做一個普通網頁沒什么區別。只不過現在,我們更常使用模板技術來解決前后端耦合的問題。

    前端使用模板引擎,在html中寫一些標簽,與數據與邏輯基本無關。后端在渲染的時候,解析這些標簽,生成HTML串,如smarty。其實前端與后端的交互在服務端就已經有一次了。

模板:

front.tpl <div> {%$a%} </div>

后端:

// 設置變量 $smarty->assign('a', 'give data'); // 展示模板 $smarty->display("front.tpl");

到前端時是渲染好的html串:

<div> give data </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所示:

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 ajax獲取數據,前端進行拼裝的方式

效果如圖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 兩種方式的權衡 

那么,如何權衡兩種方式呢?

筆者單從自己的思維考慮,得出以下結論。如果這種模板的拼裝會發生多次。是一個非常頻繁的行為,且模板基本一致,只是數據變動的話,最好是一開始采用客戶端拼裝的方法。因為,同樣的模板沒有必要被傳到客戶端好幾次。這樣,我們可以剩下傳輸同樣模板的流量,請求更快。

類似於新聞流這種網站比較適合這種方式,如今日頭條,如圖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

這種方式雖然首屏較快,但是,每次傳輸同樣的新聞模板也是需要浪費不少模板流量的。

 

2.4 混合方式

    看過了上述兩種方式,聰明的你肯定會想:如果前端的js里寫一份模板,后端的html(jsp/asp/smarty)中也寫一份模板呢?這樣,同步的時候,直接用后端HTML(jsp/asp/smarty)中的模板。異步拉取數據的時候,每次使用js中的模板進行拼裝 。同步也能保證首屏的速度,異步也能保證傳輸量的限制與速度。可是這樣,也會面臨問題,那就是,你的模板需要維護兩份。如果那天產品和你說,我要改一下頁面的結構。你不得不改動HTML的時候。js中與jsp/asp/smarty中的模板都需要同樣的更改兩次。

 

2.5 前端的模板引擎

    如果說,后端可以將html的拼裝轉變為使用引擎的話,前端為什么不可以呢?這里我先給大家寫一個非常簡單的模板解析函數,效果如圖2.5.1:

<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <div id="content1"></div> <div id="content2"></div> <script> // 這是我們的模板,怎么樣,比直接寫html字符串拼裝看起來清爽多了吧? 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/smartyMonkey

 

2.6 前后端同構

    剛剛說過了前端模板,后端模板,前端與后端都需要模板引擎。比如,我們的在后端的模板是這樣寫的:

// 接下來是偽代碼
<!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前后端一起使用的效果。一改俱改,一板兩用。其實這樣也不算極致的完美,因為聰明的讀者會發現,在頁面加載的時候,我們多傳了一份模板給到前端,如果用戶不觸發重新渲染的話,可能我們傳到前端的模板就算白傳了,造成了浪費。聰明的讀者們可以考慮一下,如何把這份也給省下去。

 

3 模板的更新


    有的時候,我們需要整片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算法及其應用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM