先推薦一個我本身寫的模板引擎catpl,項目地址:https://github.com/liyu365/catpl。
項目中有詳細的註釋,代碼易讀,本項目借鑑自artTemplate、juicer、laytpl等諸多項目,在學習這些項目的過程當中編寫而成,並遵照如下幾個步驟,能夠對照此項目代碼進行了解。javascript
例如一個模板爲:html
<h2><%= title %></h2> <ul> <% for(var i = 0 ; i < sports.length ; i++ ) { %> <li><% i + 1 %> - <% sports[i].name %></li> <% } %> </ul>
最終會編譯成爲一個函數:java
function anonymous($data, $methods) { var title = $data.title, sports = $data.sports, $escape = $methods.$escape; var $out = ''; $out += '<h2>'; $out += $escape(title); $out += '</h2><ul>'; for(var i = 0 ; i < sports.length ; i++ ) { $out += '<li>'; $out += $escape(i + 1); $out += ' - '; $out += $escape(sports[i].name); $out += '</li>'; } $out += '</ul>'; return $out; }
能夠觀察到模板中的全部的變量名都被指定成了參數$data對象的屬性,而且該函數自始至終只作了一件事,就是構建$out字符串,並將其返回。下面來梳理一下將模板編譯爲函數的過程:git
在上面的例子中<%
和%>
就是openTag和closeTag。github
任何的HTML模板都包括兩個部分:函數
HTML語句工具
邏輯表達式語句學習
openTag和closeTag的做用就是劃分出這兩個部分區別對待。<%
和%>
包裹着的爲邏輯表達式語句,在其以外的是普通的HTML語句。優化
methods.$foreach(source.split(options.openTag), function (code) { var arr = code.split(options.closeTag); if (arr.length === 1) { code_body += handle_html(arr[0]); } else { code_body += handle_logic(arr[0]); if (arr[1]) { code_body += handle_html(arr[1]); } } });
能夠看到這一步利用openTag和closeTag把模板源碼中的HTML語句傳給了handle_html()
函數,把邏輯表達式語句傳給了handle_logic()
函數。這兩個函數會把返回的代碼拼接給一個統一的變量。ui
對應上面提到的handle_html()
函數。
這一步很好理解,只要原樣輸出就好。
對應上面提到的handle_logic()
函數,這一步的核心就是分析邏輯表達式語句中的變量名,函數名,識別的方式很簡單,只要不是js中的關鍵字、保留字都視爲是一個變量名。
邏輯表達式語句又分爲兩種:
1)賦值語句,形如<%= title %>
。
直接把title視爲是一個變量名,在函數的開始處聲明title爲$data.title的引用。而後對title這個字符串原樣輸出。由於$data.title對應的值可能帶有html中的特殊字符,因此在title外面包裹了$escape函數的調用。$escape函數是一個咱們預先編寫好的函數,存放在methods對象中,而後傳和data同樣傳入到編譯函數中。
2) 邏輯控制語句,形如for(var i = 0 ; i < sports.length ; i++ ) {
依然要分析變量名,例如這裏的sports,注意分析變量名時必定要忽略.length這樣的對象屬性。
利用Function
構造函數生成一個函數:
var fun = new Function('$data', '$method', code);
這裏的code固然就是handle_html()
和handle_logic()
兩個方法翻譯後的標準js代碼拼接而成的。
fun函數還須要進一步封裝,畢竟$method尚未值。第四部已經提到,methods實際上是咱們模板引擎內部的對象,存放一些模板引擎的工具方法,咱們應該本身把它傳給編譯好的函數。其實給模板引擎註冊helper方法也是同樣的,須要咱們本身把helper對象傳進編譯函數。包裝代碼以下:
return function (data) { return fun(data, methods, helpers); };
此時返回的函數就是用戶實際拿到的函數。
其實作到第五步已是一個具有基本功能的模板引擎了,可是模板中邏輯表達式不夠簡潔,例如:
<% for(var i = 0 ; i < sports.length ; i++ ) { %>
這種表達式其實仍是原生的js語法,咱們能夠本身定義模板引擎的語法,例如:
<% foreach sports %>
這其實不難作到,只是在第四部開始以前利用正則把foreach sports
再變爲for(var i = 0 ; i < sports.length ; i++ ) {
,以後仍是同樣的流程。
catpl項目中有一個options.syntax_hook
方法就是用來定義本身的模板語法的,能夠將options.syntax_hook的值設爲null來在自定義語法和原生js語法之間切換。