寫一個簡單的模板引擎
阿新 • • 發佈:2019-06-02
寫一個簡單的模板引擎
ES5 開始支援模板字串(Template literals),支援如下的寫法:
`string text ${expression} string text`;
其實在很多模板引擎中,常常會有這樣需求,比如常用的 doT,使用類似的語法
<div>{{=1+2}}</div>
// 或者支援迴圈或者判斷 {{for(var i in it){}}}
<span>{{=i}}</span>
{{}}}
簡單插值的實現
我們先來看看一個模板引擎基本的實現需要什麼,先不考慮迴圈和判斷,只支援變數運算。
開啟Babel,輸入
const a = 1; console.log(`Hi\n${2 + 3}!dk${a}`);
經過Babel
轉義以後,可以看到
"use strict";
var a = 1;
console.log("Hi\n".concat(2 + 3, "!dk").concat(a));
可以看到,Babel
把插值提取到 concat 入參,通過函式入參的自計算實現了 Template literals。在我們的使用中,其實沒法直接做到這樣的效果。
但是仿造Babel
的做法,我們可以整理一下自己的思路:
- 通過正則把插值和實際字串拆開
- 通過
eval
或者new Function()
實現插值的計算 - 通過 concat 拼接,也可以使用 String.raw
程式碼實現如下:
var str = "string text ${1 + 2} string text ${2 + 3} test"; function template(str) { var pattern = /\$\{.*?\}/g; var patternCapture = /\$\{(.*?)\}/g; // 將非插值字串分割出來 var strArr = str.split(pattern); // 將插值字串分割出來 var rawArr = str .match(patternCapture) .map(item => item.replace(patternCapture, "$1")); // eval轉換 var valueArr = rawArr.map(r => eval(r)); // 使用reduce和concat拼接, return strArr.reduce( (acc, curr, index) => acc.concat(curr, valueArr[index] || ""), "" ); // 或者使用String.raw // return String.raw({ raw: strArr }, ...valueArr); } console.log(template(str));
new Function
上面使用eval
對插值進行了求值,實際上在平時使用中,eval
是不推薦的。而且用eval
去解析一些迴圈判斷和條件判斷也不是很方便。
所以接下來使用new Function()
去構建一個模板函式。在這之前,需要說明一下new Function
的用法。
new Function ([arg1[, arg2[, ...argN]],] functionBody)
前面傳入的是函式所需要的引數,最後是函式體,函式體是一個包括函式定義的 JavaScript 語句字串。
其次,根據上面的插值實現,我們可以使用字串拼接把插值計算之後和正常的字串拼接起來。
對於簡單插值,使用{{}}
包裹,而語句使用{{~}}
區別。下面是一個簡單實現
function render(tem, data) {
let template = tem;
template = template
.replace(/[\r\n\t]/g, "")
.replace(/\{\{~(.+?)\}\}/g, (_, p1) => {
return '";' + p1 + ' out+="';
})
.replace(/\{\{(.+?)\}\}/g, (_, p1) => {
return '"; out+=""+' + p1 + '+""; out+="';
});
template = 'var out=""; out += "' + template + '";return out;';
var _render = new Function(...Object.keys(data), template);
return _render(...Object.keys(data).map(k => data[k]));
}
var template =
"test array{{~for (var i in group.jobs) {}}{{group.jobs[i]}} {{~}}} test obj {{group.jobs[1]}} {{group.name}} leader是{{leader}}";
var data = {
group: {
name: "group1",
jobs: ["job1", "job2"]
},
leader: "張三"
};
console.log(render(template, data));
在給模板注入資料時,可以使用with(data){}
的方式,我不喜歡使用with
,所以把引數分解後傳入了。
(完)