javascript編寫自己的模板解析器
編寫自己的模板解析器
因為最近在研究artTemplate,ejs,baaiduTemplate等模板,所以,一時興起,自己也寫了個簡單的模板解析器。
一個最基本的模板解析器,需要有什麼功能呢?
讀取變數值 解析模板語句
按照這個思路,我們編寫一個簡單的解析器,需求如下:
讀值: <%= 變數名 %> 語句支援: <% if( type == 1 ){ %> good! <%}%>
總體來說,就是如果模板如下:
我叫:<%= name %> <% if(type == 1){ %> 狀態: <%= type %><% } %>
如果有資料{name: ‘da宗熊’ type: 1},則需要輸出:
我叫: da宗熊狀態: good!
按照上面需求,我們需要做什麼呢?因為考慮到要在模板內執行指令碼,所以,最直接的辦法, 就是把模板翻譯為 javascript。
動手咧變數
觀看了上面幾個模板引擎的代碼,主要思路莫過於:
通過正則匹配,找出 ‘<%…%>’ 的模組,進行處理 把所有匹配出來的內容,拼接為 字串 然後通過 new Function 產生可執行檔函數
但3者,對於 <%= 變數 %> 的取值,有著不同的處理:
ejs通過with語句,給變數指定了變數的訪問上下文。
artTemplate和baiduTemplate通過遍曆傳入的變數,產生變數聲明語句,從而達到給變數賦值的效果。
考慮到with語句的低效,這裡採取了 第二種 形式。
// 如有傳入的變數參數為:datavar data = { name: 'da宗熊', type: 1};// 那模板中,遍曆變數,產生聲明語句var varStatement = '';for(var name in data){ varStatement += 'var ' + name +' = data.' + name + ';';}// varStatement = 'var name = data.name;var type = data.type;';
函數
第二部,也是最重要的,莫屬於解析模板的語句了。
因為模板,經常會是某個元素的innerHTML,而它的換行[呃,我不擅長控制],一般我會替換掉:
<script> var html = template.innerHTML; // 替換掉換行: html = html.replace(/s/g, );</script>
考慮到, ‘xx內容<% 運算式 %>’ 這種形式比較好用正則匹配,所以,把 ‘前面內容<%運算式%>後面內容’ 的 ‘前面’ 和 ‘後面’ 部分去掉:
// resList 是最終的結果,rightContent是‘後面’部分的內容var resList = [], rightContent = '';html = html.replace(/(.*?)(<%.*%>)(.*)/g, function(str, left, center, right){ rightContent = right; resList.push(left); console.log(left:%s center:%s right:%s, left, center, right); return center;});// 正則匹配的內容將是:// left: 我叫:// center: <%= name %> <% if(type == 1){ %> 狀態: <%= type %> <% }%>// right:
剩下 ‘center’ 的內容,都符合 ‘xx內容<%運算式%>’ 的格式,所以,使用正則,編譯剩下的內容:
var __TMP_LIST__ = '__TMP_LIST__', __TMP_DATA__ = '__TMP_DATA__';// 需要編譯的動態函數內容var fnStr = '';// 開始匹配運算式,匹配 'xxx<% expr %>'html.replace(/(.*?)<%(.*?)%>/g, function(str, html, exp){ console.log(html:%s exp:%s, html, exp); // html內容 fnStr += __TMP_LIST__ + .push(' + quote(html) + ');; // 運算式內容,帶等號,取值,不帶,則是動態函數的邏輯控制部分 if(exp.indexOf(=) === 0){ fnStr += __TMP_LIST__ + .push(+ exp.slice(1) +);; }else{ fnStr += exp; }});
產生動態函數
準備工作都完成了,剩下最後一步:
// 構建編譯的動態函數var fn = new Function(__TMP_DATA__, __TMP_LIST__, fnStr);// 如果結果清單沒有資料,則沒有需要編譯的內容if(resList.length > 0){ // 把需要編譯的資料,結果清單,傳入動態函數 fn(data, resList); // 不要漏了最後一個小尾巴,上面去除 ‘前’ 和 ‘後’部分,留下的 resList.push(rightContent); console.log(resList.join());}else{ console.log(html);}
一個簡答的模板編譯函數,已經大功告成了~。
最後
最除版本,肯定有很多BUG,不過思路應該沒有錯的。還有挺多拓展的,像轉義,錯誤偵測什麼的,配置定界符什麼的,都可以嘗試,這裡就不再詳細介紹了。