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.indexif (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复制代码