初識前端模板

【原文】作者:yaya() 王集鵠()


總述

  “模板”這個詞,可能很多人第一印象是後端的技術(Smarty,Velocity等),但本文要講的卻不是後端的概念,而是前端開發中所使用到的一種技術,也就是“前端模板”技術。

  模板的工作原理可以簡單地分成兩個步驟:模板解析(翻譯)和數據渲染。這兩個步驟可分別部署在前端或後端來執行。如果都放在後端執行,則是像Smarty這樣的後端模板,而如果都放在前端來執行,則是我們要探討的前端模板。

問題

  隨着前端交互的複雜性不變提升,無刷新頁面數據傳輸與渲染越發地頻繁化,我們發現傳統的前端開發方式在ajax數據渲染等方面存在着一個主要問題:繁瑣的數據渲染。當前端從後臺通過ajax等方式或許到數據後,如果要將這個數據渲染到指定的dom元素中,則需要進行各種字符串拼接工作或者一系列創建元素的工作,還不論細節的問題(單引號雙引號問題等),不管是哪一種形式,都是繁瑣且費時的。同時,在可讀性與維護性上也存在問題。試想,各種循環操作的字符串拼接,元素創建插入,在需要修改時,都需要重新花費不少時間與精力。那有什麼方法可以解決這個問題呢?

原理

  當我們在JSP中寫 <ul><li><%=name%></li></ul> 的時候,其實就是在應用模板,在後臺這句話會被轉換成out.print("<ul><li>"+name+"</li></ul>") 。模板的數據渲染就是把模板中的佔位符(這裏是“name”),替換成傳入的值(比如替換成“yaya”)。而在前端開發中,這種方式依然具有很高實用價值。前端模板的核心是前端模板引擎,引擎將前端的模板語言轉換成瀏覽器可以解析的html語言。當轉換成功後,便可以很方便地將這段html代碼放到我們希望的地方去。

  比如我們可以寫一段循環的li標籤的前端模板語言。通過前端模板引擎轉換後成本一連串得li標籤的html語言。這時候就可以直接採用innerHTML方法把html代碼插入到ul對象中,那麼就完成了生成ul列表的功能。

初識

  前端的模板核心是模板解析引擎,而解析引擎的主要作用是將模板語言轉換成html/xml格式。不同的前端模板有着不同的模板語言,解析引擎因此也各不相同。讓我們先來認識幾款前端模板,瞭解下它們各自的模板語言。

  YayaTemplate?是一款輕量級的模板引擎,採用原生javascript語法,具有易學易用等特點。我們來看一段用YayaTemplate?渲染列表數據的實例:

  模板語言(通用過for循環,輸出“這是第n列”的li列表)

for(var i=0;i<list.length;i++){
       {$ <li>這是第 {% i %} 列:{% list[i] %}</li> $}
}

  有了模板語言後,我們只需要將數據“打入”模板語言中的“list”,就可以生成我們想要的html/xml格式了。如上例,我們只需要得到這個模板語言進行翻譯,並調用對應的render方法,

var list = ["紅桃", "方塊", "梅花", "黑桃"];
var html = YayaTemplate(templateText).render({list:list});

  這個html則是模板引擎轉換成的html/xml語言,在上例中,則爲:

<li>這是第0列:紅桃</li>
<li>這是第1列:方塊</li>
<li>這是第2列:梅花</li>
<li>這是第3列:黑桃</li>

  從這個例子中,我們可以發現,{$...$}表示輸出的html/xml片段,{%...%}表示輸出javascript變量。得到的html,我們可以用直接作爲dom的innerHTML或者其他用處。

  這便是前端模板,它使得我們不必去處理字符串拼接等問題,用最直觀的方式來渲染數據。我們再來看另外一款前端模板 EasyTemplate? 。還是之前的例子,用EasyTemplate?模板寫法如下:

<#list data as list>
<li>這裏是第${list_index} 列:${list}</li>
</#list>

  同樣,需要將實際數據替換模板變量,這裏採用:

var list = ["紅桃", "方塊", "梅花", "黑桃"];
var html = easyTemplate.render(templateText, list); //templateText指模板語言

  像EasyTemplate?這樣的前端模板,是屬於自定義模板語言的一種前端模板。我們可以從上例看出,“<#list>”就是EasyTemplate?自定義的循環條件。像這樣EasyTemplate?模板一樣採用自定義標籤的前端模板還有LiteTemplate?

<c:for var="item" list="${list}">
   <li>這裏是第${for.index}列:${item}</li>
</c:for>

  渲染時採用:

var list = ["紅桃", "方塊", "梅花", "黑桃"];
var html = liteFunction(templateText, 'list')(list);

  好了,我們再來看看jquery作者John Resig所寫的一個前端模板jQuery Template。說真的,它如同jQuery一樣,短小精悍。還是老例子:

<% for(var i = 0; i < list.length; i++) { %>
<li>這是第 <%=i%> 列:<%= list[i] %></li>
<% } %>

  渲染採用:

var list = ["紅桃", "方塊", "梅花", "黑桃"];
var html = tmpl("templateid", list);

  我們可以看出,YayaTemplate?與jQueryTemplate在模板語言的寫法上正好相反。前者將輸出html語言做特殊標記{$...$},而後者對javascript語言做特殊標記<%...%>。這兩種模板已經使得學習成本很低了,而接下來介紹的AceTemplate的寫法將更加簡單易懂。

for (var i = 0; i < list.length; i++) {
<li>這是第 #{i} 列:#{list[i]}</li>
}

  渲染採用:

var list = ["紅桃", "方塊", "梅花", "黑桃"];
var html = AceTemplate.format("templateid", {list:list});

  AceTemplate採用了html與js語言直接混搭的風格,在兩者間可以直接的書寫,不用添加任何的標誌用以區分不同的語言。而在html語言裏面,使用js變量則採用#{}的方式輸出。AceTemplate之所以可以兼容html與js混合寫法,是通過按行解析來實現的。所以,如果代碼能夠保證html語言與js按行劃分,這樣的用法其實是很方便的。並且AceTemplate值得說明的一點是支持自動編碼防止xss漏洞,通過#{}渲染出來的javascript變量,已經經過了編碼處理,這一點是很方便的。而對於不需要這個功能,需要原文輸出的時候,AceTemplate也提供了!#{}方法來滿足這種需求。

  通過上面對四個前端模板的簡單介紹,我們可以瞭解到不同前端模板的各種形態,這包括寫法與用法等直觀印象。但我們知道,前端模板的核心是代碼的轉換,這肯定是需要轉換時間的,那就以上四個不同的前端模板,性能上會有怎樣不同的表現呢。

性能

  前端模板語言到html/xml語言,是通過模板引擎進行翻譯的。而模板引擎的翻譯性能在某種程度上決定了前端模板解決方案的可行性的高低。上訴四種前端模板,各自的性能會是怎麼樣的,我們對它們進行測試。分別對YayaTemplate?、EasyTemplate?、jQueryTemplate、AceTemplate、LiteTemplate?部署前端模板做同樣的操作,比較模板引擎翻譯時間代價。

各自的模板代碼如下:

YayaTemplate?

for (var i = 0; i < list.length; i++) {
    if (i<100){
        $<li>小於100 這裏是第{%i%} 列:{%list[i]%}</li>$}

    }
    else {
        {$<li>不小於100 這裏是第{%i%} 列:{%list[i]%}</li>$}

    }
}

EasyTemplate?

<#list data as list>
    <#if (list_index < 100)>
        <li>小於100 這裏是第${list_index} 列:${list}</li>
    <#else>
        <li>不小於100 這裏是第${list_index} 列:${list}</li>
    </#if>
</#list>

jQueryTemplate

<% for (var i=0;i<list.length;i++){ %>
    <% if (i<100) { %>
        <li>小於100 這裏是第<%=i%>列:<%=list[i]%></li>
    <% } else{  %>
        <li>不小於100 這裏是第<%=i%>列:<%=list[i]%></li>
    <% } %>
<% } %>

AceTemplate

for (var i = 0; i < list.length; i++) {
    if (i<100){
        <li>小於100 這裏是第#{i} 列:#{list[i]}</li>
    }
    else{
        <li>不小於100 這裏是第#{i} 列:#{list[i]}</li>
    }
}

LiteTemplate?

<c:for var="item" list="${list}">
    <c:if test="${for.index&lt;100}">
        <li>小 於100 這裏是第 $!{for.index} 列:$!{item}</li>
    </c:if>
    <c:else>
        <li>不小於100 這裏是第$!{for.index} 列:$!{item}</li>
    </c:else>
</c:for>

  然後我們改變list數組裏面的元素個數,對各個模板翻譯執行的時間進行記錄。結果如下(xp+ie6/ie8/firefox/chrome運行環境):

   模板翻譯時間對比表(第一次翻譯並渲染數據 時間單位:毫秒)

  通過第一次翻譯後,如果前端模板可以緩存翻譯後的中間代碼,或者可以返回構建中間代碼的函數,那麼再次渲染數據的時候,就不需要再翻譯。這樣可以極大的縮小渲染數據的時間,提高速度。

   綜合各種調研數據對比表如下:

兼容

  前端模板的兼容性也是一個重要的問題。能夠實現用戶不同的前端模板需求,將前端模板語言正確翻譯成html/xml語言,是優秀的前端模板所需要具備的特點。而通過對以上四款前端模板的測試,並沒有發現嚴重的兼容性問題。但在一些細節上,還是發現了一些問題如下表:

  兼容性測試對比表

流程

  對於“什麼是前端模板,它有什麼特性,怎麼使用”這樣的問題已經通過上面的分析說明給出了答案。但前端模板既然是前端的範疇,就不可能獨立存在,而是需要運用到前端開發的流程中的。而採用了前端模版的開發流程與傳統的相比又會是怎麼樣的呢?

  上圖是傳統的開發流程。首先將ui設計圖轉換成html的頁面,其中的數據一般先用模擬數據代替。比如,ui設計有個列表,那麼可能開發人員會先建立一些模擬的數據填充到節點中,來開發調整頁面樣式。最後一步,則將需要動態生成(ajax應用等)的地方,將模擬數據的節點變成空白節點,然後在javascript裏面拼裝這些html節點的字符串,最後再還原到原節點處(比如用innerHTML插入html)。

  上圖是一個實例。當列表中的元素需要ajax動態加載的時候,在傳統開發中可能按照先開發模擬數據的html頁面,再將這些元素拼接成html字符串,之後再進行一系列處理的功能。

  那麼,它的問題是什麼呢?很明顯,“不可逆”是最大的問題。當開發者完成了開發,這時候如果需要修改,那麼將是很頭疼的事。由於是由字符串拼接出來的html片段,想直接修改這些字符串來改變結構或是修改樣式什麼的將是一個比重新開發一遍還要具有挑戰的工作。所以,開發者往往選擇再來一遍吧:html的模擬數據頁面,然後再拼接字符串。除了“不可逆”,維護性差以及開發成本高都是採用傳統方式開發富客戶端應用的弊病。

  好吧,我們試着改變這個局面。看看下圖,採用前端模板開發的新方式,或許會找到某些答案。

  “雙向可逆”,是的,採用前端模板的開發方式,在開發好展示的html頁面後,直接經過簡單的修改即可生成html+template 頁面,無需再拼接字符串,無需再反覆重寫展示模擬數據的html頁面,一切都變得很輕鬆。我們來看看代碼便知道原因了(以ace template爲例)。

  如果調用模板引擎,當模板執行數據執行後,直接覆蓋parentNode裏面的內容。而如果想繼續調整html結構等,則不調用模板引擎即可。而原有的調試數據,在需要發佈的時候可以直接通過代碼編譯去掉debug start與debug end之間代碼即可(這僅僅是前端模塊開發的一種實例,實際開發中可以去掉模擬數據,不用編譯)。

展望

  前端模板技術其實還有很多的工作要做,比如模板的事件代理,模板的複用性,模板的組件庫等等。本文僅對前端模板做了一個大致講解。相信隨着對於前端模板的探索,模板技術會被越來越多得運用的前端開發,特別是富客戶端的前端開發中,進一步提高開發效率,爲開發人員帶來更多的驚喜!

參考資料

  1. yaya template
easy templatequery templateace templatelite template
發佈了9 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章