handlebars 是一款优秀的模板引擎,其基本的使用方法如下:
const str = `My name is {{name}}, I'm {{age}} years old`
const data = {name: 'keliq', age: 10}
console.log(require('handlebars').compile(str)(data))
// 得到:My name is keliq, I'm 10 years old
内部究竟是如何实现的呢?其实只需要三步:
第一步:解析模板
解析模板的目的就是把下面的字符串:
My name is {{name}}, I'm {{age}} years old
变成下面的数组:
[ 'My name is ', '{{name}}', ", I'm ", '{{age}}', ' years old' ]
解析函数如下:
var parse = (tpl) => {
let result, firstPos
const arr = []
while (result = /{{(.*?)}}/g.exec(tpl)) {
firstPos = result.index
if (firstPos !== 0) {
arr.push(tpl.substring(0, firstPos))
tpl = tpl.slice(firstPos)
}
arr.push(result[0])
tpl = tpl.slice(result[0].length)
}
if (tpl) arr.push(tpl)
return arr
}
第二步:构造表达式
构造表达式就是把第一步得到的解析结果:
[ 'My name is ', '{{name}}', ", I'm ", '{{age}}', ' years old' ]
转换成下面的 JS 表达式:
""+"My name is "+data.name+", I'm "+data.age+" years old"
这一步的实现相对比较简单,就是拼字符串,代码如下:
const compileToString = (tokens) => {
let fnStr = `""`
tokens.map(t => {
if (t.startsWith("{{") && t.endsWith("}}")) {
fnStr += `+data.${t.split(/{{|}}/).filter(Boolean)[0].trim()}`
} else {
fnStr += `+"${t}"`
}
})
return fnStr
}
第三步:创建渲染函数
我们在第二步已经得到了 JS 表达式,但本质上还是一个字符串而已:
""+"My name is "+data.name+", I'm "+data.age+" years old"
那如何执行呢?通过 new Function
动态创建函数可以做到这一点:
const compile = (tpl) => {
return new Function("data", "return " + compileToString(parse(tpl)))
}
这就实现 handlebars 的 compile 函数了,不妨运行一下看看:
console.log(compile(str)(data))
// My name is keliq, I'm 10 years old