简介
模板引擎,其实就是一个根据模板和数据输出结果的一个工具。
我们要开发一个将模板文件转换成我们实际要使用的内容的工具,这个工具就是模板引擎。我们把模板文件里的内容当成字符串传入到模板引擎中,然后模板引擎根据一定语法对该字符串进行解析处理,然后返回一个函数,之后我们在执行函数时把数据传输进去,即可拿到根据模板和数据得到的新字符串。最后我们想怎么处理该字符串就看需求了,如果用于前端模板生成的话,则可以用 dom 的 innerHTML 这个属性来追加内容。
目前前端的模板引擎多得数不胜数,语法特性也花样百出,用行内的话来说,我们要实现的是一种基于字符串的模板引擎。
简要概述流程如下:
1 |
模板 ----> 输入到模板引擎 ----> 生成函数 ----> 把数据当成参数,执行该函数 ----> 输出结果 |
优劣
- 此模板引擎可用于任意一端,前端后端即插即用,不局限于生成内容的语法,只要生成内容为字符串文本即可。比如在合并 Sprite 图工具中要根据图片大小位置生成对应的 css 定位文件,我们也可以用该引擎生成而不需要另外再写一套引擎。
- 此模板引擎对于数据的更改,需要重新渲染一遍模板,所以在初次渲染和之后的模板更新需要耗费同样的资源。
- 应用于前端时,此模板引擎依赖于 innerHTML,存在注入问题。
需求
而此次,我们希望实现一个基于字符串的模板引擎。提供的使用方式尽可能简单,比如类似如下的方式:
1 2 3 4 5 6 7 8 9 10 |
// 前端 var html = window.parse('<div>${content}</div>', { content: 'june' }); // 后端 const parse = require('tpl'); var html = parse('<div>${content}</div>', { content: 'june' }); |
并且希望至少提供以下四种语法:
条件判断
1 2 3 4 5 6 7 |
{if condition1} // code1 {elseif condition2} // code2 {else} // code3 {/if} |
数组遍历
1 2 3 4 |
{list array as item} // code // PS:里面注入了一个变量item_index,指向item在遍历过程中的序号 {/list} |
变量定义
1 |
{var var1 = 1} |
插值
1 2 3 4 5 |
// 直接插值 ${var1} // 使用过滤器插值的方式 ${var1|filter1|filter2:var2, var3} |
开工
STEP 1
按照前面定下的需求,我们先实现一个对外的接口,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
'use strict'; var __PARSE__ = (function() { /** * 默认的过滤器 */ const defaultFilter = { // some code }; /* * 解析模板 */ let doParseTemplate(content, data, filter) { // some code }; return function(content, data, filter) { try { data = data||{}; filter = Object.assign({}, defaultFilter, filter); // 解析模板生成代码生成器 let f = doParseTemplate(content, data, filter); return f(data, filter); } catch(ex) { return ex.stack; } }; })(); if(typeof module !== 'undefined' && typeof exports === 'object') { module.exports = __PARSE__; } else { window.parse = __PARSE__; } |
此处,f 即是我们生成的函数,而生成该函数的函数我命名为 doParseTemplate,接收三个参数,content 是我们输入的模板文件的字符串内容,data 是我们要传入的数据,而 filter 即为模板中可传入的过滤器。目前 doParseTemplate 这个函数还未实现,接下来就来实现此函数。
STEP 2
为了生成一个可用的函数,我们要通过 new Function('DATA', 'FILTER', content); 这样的方法来构造一个函数,其中 content 即是函数体的字符串内容。
我们先设定要生成的函数 f 的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |