说说如何用JavaScript实现一个模板引擎

发表于:2017-3-21 09:47

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:whxaxes    来源:51Testing软件测试网采编

分享:
  实现多级作用域
  在将 ast 转换成 html 的时候,有一个很常见的场景就是多级作用域,比如在一个 for 循环中再嵌套一个 for 循环。而如何在做这个作用域分割,其实也是很简单,就是通过递归。
  比如我的对一个 ast 树的处理方法命名为:processAst(ast, scope),再比如最初的 scope 是
{
list: [
{ subs: [1, 2, 3] },
{ subs: [4, 5, 6] }
]
}
  那么 processAst 就可以这么实现:
function processAst(ast, scope) {
...
if(ast.for) {
const list = scope[ast.item]; // ast.item 自然就是列表的 key ,比如上面的 list
list.forEach(item => {
processAst(ast.children, Object.assign({}, scope, {
[ast.key]: item,  // ast.key 则是 for key in list 中的 key
}))
})
}
...
}
  就简单通过一个递归,就可以把作用域一直传递下去了。
  Filter 功能实现
  实现上面功能后,组件就已经具备基本的模板渲染能力,不过在用模板引擎的时候,还有一个很常用的功能就是 filter 。一般来说 filter 的使用方式都是这这样 {{ test | filter1 | filter2 }},这个的实现也说一下,这一块的实现我参考了 vue 的解析的方式,还是蛮有意思的。
  还是举个例子:
  {{ test | filter1 | filter2 }}
  在构建 AST 的时候,就可以获取到其中的test | filter1 | filter2,然后我们可以很简单的就获取到 filter1 和 filter2 这两个字符串。起初我的实现方式,是把这些 filter 字符串扔进 ast 节点的 filters 数组中,在渲染的时候再一个一个拿出来处理。
  不过后来又觉得为了性能考虑,能够在 AST 阶段就能做完的工作就不要放到渲染阶段了。因此就改成 vue 的方法组合方式。也就是把上面字符串变成:
  _$f('filter2', _$f('filter1', test))
  预先用个方法包裹起来,在渲染的时候,就不需要再通过循环去获取 filter 并且执行了。具体实现如下:
const filterRE = /(?:\|\s*\w+\s*)+$/;
const filterSplitRE = /\s*\|\s*/;
function processFilter(expr, escape) {
let result = expr;
const matches = expr.match(filterRE);
if (matches) {
const arr = matches[0].trim().split(filterSplitRE);
result = expr.slice(0, matches.index);
// add filter method wrapping
utils.forEach(arr, name => {
if (!name) {
return;
}
// do not escape if has safe filter
if (name === 'safe') {
escape = false;
return;
}
result = `_$f('${name}', ${result})`;
});
}
return escape ? `_$f('escape', ${result})` : result;
}
  上面还有一个就是对 safe 的处理,如果有 safe 这个 filter ,就不做 escape 了。完成这个之后,有 filter 的 variable 都会变成_$f('filter2', _$f('filter1', test))这种形式了。因此,此前的 computedExpression 方法也要做一些改造了。
function processFilter(filterName, str) {
const filter = filters[filterName] || globalFilters[filterName];
if (!filter) {
throw new Error(`unknown filter ${filterName}`);
}
return filter(str);
}
function computedExpression(obj, expression) {
const methodBody = `return (${expression})`;
const funcString = obj ? `with(_$o){ ${methodBody} }` : methodBody;
const func = new Function('_$o', '_$f', funcString);
try {
const result = func(obj, processFilter);
return (result === undefined || result === null) ? '' : result;
} catch (e) {
// only catch the not defined error
if (e.message.indexOf('is not defined') >= 0) {
return '';
} else {
throw e;
}
}
}
  其实也是很简单,就是在 new Function 的时候,多传入一个获取 filter 的方法即可,然后有 filter 的 variable 就能被正常识别解析了。
  至此,AST 构建、AST 到 html 的转换、多级作用域以及 Filter 的实现,都已经基本讲解完成。
  贴一下自己实现的一个模板引擎轮子:https://github.com/whxaxes/mus
  算是实现了大部分模板引擎该有的功能,欢迎各路豪杰 star 。
22/2<12
精选软件测试好文,快来阅读吧~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号