模板的工作原理可以簡單地分成兩個步驟:模板解析(翻譯)和數據渲染。這兩個步驟可分別部署在前端或后端來執行。如果放在后端執行,則是像Smarty,FreeMarker這樣的后端模板引擎,而如果放在前端來執行,則是我們要探討的前端模板。
FreeMarker是一個模板引擎,一個基於模板生成文本輸出的通用工具,使用純Java編寫,模板用servlet提供的數據動態地生成 HTML,模板語言是強大的直觀的,編譯器速度快,輸出接近靜態HTML頁面的速度。這里不再對后端模版進行描述。
前端模版提高了前端開發的可維護性(后期改起來方便)以及可擴展性(想要增加功能,增加需求方便);提高了開發效率提高(程序邏輯組織更好,調試方便);最重要的一點就是:【視圖(包括展示渲染邏輯)與程序邏輯的分離】。好處是減輕服務器負擔,壞處是可能不利於seo以及模版錯誤不好調試。
當今前端模版主要有三類:-String-based 模板技術 (基於字符串的parse和compile過程) -Dom-based 模板技術 (基於Dom的link或compile過程) -雜交的Living templating 技術 (基於字符串的parse 和 基於dom的compile過程)。
一.前端模版的演變
傳統的前端開發方式是通過通過ajax獲取數據進行繁瑣的數據渲染。隨着前端頁面的交互越來越繁雜,頁面無刷新的傳輸與頁面的顯然也越發的頻繁,導致頁面性能低下。即當前端從后台通過ajax等方式獲取到數據更新后,都需要將這個數據渲染到指定的dom元素中,需要重新進行各種字符串拼接工作或者一系列創建元素的工作,這種方式是繁瑣且費時的。這種在可讀性和維護性上也存在問題。
基於字符串的模板引擎最大的功勞就是把你從大量的夾帶邏輯的字符串拼接中解放出來了,由於它的完全基於字符串的特性,它擁有一些無可替代的優勢。如下的字符串拼接:

Dom-based的模板技術中,如果你需要從一段字符串創建出一個view,你必然通過innerHTML來獲得初始Dom結構. 然后引擎會利用Dom API(attributes, getAttribute, firstChild… etc)層級的從這個原始Dom的屬性中提取指令、事件等信息,Dom-based的模板技術並沒有完整的parse的過程。繼而完成數據與View的綁定,使其”活動化”。所以Dom-based的模板技術更像是一個數據與dom之間的“鏈接”和*“改寫”*過程。 完成compile之后,data與View仍然保持聯系,即你可以不依賴與手動操作Dom API來更新View。
String-based 和 Dom-based的模板技術都或多或少的依賴與innerHTML, 它們的區別是一個是主要是為了Rendering 一個是為了 Parsing 提取信息。Living Template Engine模版引擎的解析過程類似於String-based 模板技術 和 compile過程類似於Dom-based模板技術。
二.String-based 模板技術 (基於字符串的parse和compile過程)

抽象語法樹(Abstract Syntax Tree)也稱為AST語法樹,指的是源代碼語法所對應的樹狀結構。也就是說,對於一種具體編程語言下的源代碼,通過構建語法樹的形式將源代碼中的語句映射到樹中的每一個節點上。

實現一個簡單的字符串循環模版:

上面的例子很好的說明了String-based 模板技術的原理。它產生html結構,直接通過innerHTML插入到DOM中。
優點:相對於字符串拼接,實現了模版和代碼邏輯的分離,不用大量的字符串拼接 缺點:render之后數據即與view完全分離,innerHTML的性能問題,安全問題等
三.Dom-based 模板技術 (基於Dom的link或compile過程)

先通過innerHTML來獲得初始Dom結構,然后引擎會利用Dom API(attributes, getAttribute, firstChild… etc)層級的從這個原始Dom的屬性中提取指令、事件等信息。繼而完成數據與View的綁定,使其”活動化”。

Node對象定義了一系列屬性和方法,來方便遍歷整個文檔。用parentNode屬性和childNodes[]數組可以在文檔樹中上下移動;通過遍歷childNodes[]數組或者使用firstChild和nextSibling屬性進行循環操作,也可以使用 lastChild和previousSibling進行逆向循環操作,也可以枚舉指定節點的子節點。而調用appendChild()、insertBefore()、removeChild()、replaceChild()方法可以改變一個節點的子節點從而改變文檔樹。需要指出的是,childNodes[]的值實際上是一個NodeList對象。因此,可以通過遍歷childNodes[]數組的每個元素,來枚舉一個給定節點的所有子節點。
在 JavaScript 中也有很多樹形結構。比如 DOM 樹,省市區地址聯動,文件目錄等; JSON 本身就是樹形結構。通過遞歸,可以枚舉樹中的所有節點。
Dom-based 模板技術的實現過程會包含在下面Living Template Engine的敘述中。
四.Living Template Engine
String-based 和 Dom-based的模板技術都或多或少的依賴與innerHTML, 它們的區別是一個是主要是為了Rendering 一個是為了 Parsing 提取信息。所以為什么不結合它們兩者來完全移除對innerHTML的依賴呢?parse和compile的過程分別類似於String-based 模板技術 和 Dom-based模板技術。

先調用Parser()模塊對字符串進行解析輸出AST,這個方法模板內部將包含對模板的詞法分析、語法分析、構造輸出AST。然后調用this.$compile(AST)方法編譯,這個方法里調用walkers進行遞歸遍歷這個AST,最后輸出並保存這個組件的Dom,當調用這個組件的$inject()方法就可以把這個Dom插入到頁面中。
1 . Parsing
首先我們使用一個內建DSL來解析模板字符串並輸出AST。
1)詞法分析器又稱為掃描器,詞法分析是指將文本代碼流解析為一個個記號,分析得到的記號以供后續的詞法分析使用。 這個模塊在Regular頂級模塊執行過程中調用Parse模塊進行語法分析前會調用Lexer詞法分析模塊對字符串模板進行詞法分析。詞法分析的主要流程如下圖所示:

詞法分析主要分為兩部分進行,分別是Tag類型元素字符串,還有一類是JST字符串。通過全局中全局中保存一個state狀態,當前解析完成后,會判斷一個字符串的開頭是否以“<”字符開始,如果是則進入Tag詞法解析流程,如果不是則進入JST模板詞法解析流程。 最后通過詞法分析,將得到一個很長的數組,這個數組中裝着一個個上面的詞法對象,這將為之后的語法分析做下鋪墊。
2)在詞法分析模塊部分,將解析詞法分析出的詞塊,然后根據Regular模板語法,拼接零散的詞塊為具體含義的語法對象,然后輸出一棵抽象語法樹AST,這是進行下一步編譯this.$compile()的輸入。 首先要定義出這個抽象語法樹每個節點對象的類型以及它含有的屬性。

一一輸入詞法分析出來的詞法塊,根據這個詞法塊的type類型的不同來執行不同的邏輯,他們的本質都是根據當前type類型去判斷,取出之后的詞法塊的一定個數,然后通過創建出特定的語法節點對象。
最終就成功得到了一棵由7種語法節點對象組成的抽象語法樹AST。這將為之后的編譯做下鋪墊。
例如,在regularjs中,下面這段簡單的模板字符串

會被解析為以下這段數據結構

2.Compiler
結合特定的數據模型(在regularjs中,是一個裸數據), 模板引擎層級游歷AST並遞歸生成Dom節點(不會涉及到innerHTML). 與此同時,指令、事件和插值等binder也同時完成了綁定,使得最終產生的Dom是與Model相維系的,即是活動的.
通過上一節已經得到了一棵AST,這課抽象語法樹的節點是7中節點的一種,這個時候只要通過先序遍歷[17]這個AST,然后根據語法塊的type類型執行不通過的構造函數創建出Dom對象即可。
以上面的模板代碼的一個插值為例:"{{isLogin? 'Login': 'Wellcome'}}"。一旦regularjs的引擎遇到這段模板與代表的語法元素節點,會進入如下函數處理

正如我們所見, 歸功於$watch函數,一旦表達式發生改變,文本節點也會隨之改變,這一切其實與angularjs並無兩樣(事實上regularjs同樣也是基於臟檢查)
與Dom-based 模板技術利用Dom節點承載信息所不同的是,它的中間產物AST 承載了所有Compile過程中需要的信息(語句, 指令, 屬性…等等). 這帶來幾個好處
輕量級, 在Dom中進行讀寫操作是低效的. 可重用的. 可序列化 , 你可以在本地或服務器端預處理這個過程。 安全, 因為安全不需要innerHTML幫我們生成初始Dom。
小結:
后端模板可以承載頁面的固定數據,如登陸的webUser,它隨着頁面的產生而產生,隨着頁面的消失而消失;前端模板主要實現復雜的頁面交互伴隨的數據變化,進行頁面無刷新的數據更新,實現頁面的多彩化。所以前端模板和后端模板要相互結合使用,才能更好的服務於web應用。