当下前端充斥着各类各样的开发框架:React,Vue 等等。然而大多数这些框架的设计模式是采用了以数据为核心的 MVVM 模式。MVC 的开发模式已经离咱们渐行渐远。javascript
对于 MVVM 模式来讲,最核心的部件就是一个围绕数据的模板引擎。html
模板引擎分为前端和后端前端
今天咱们就来深刻研究一下客户端模板引擎的实现!java
模板引擎分为两个部分:git
由此咱们就能够写下咱们的第一行代码:github
/** * @param {String} template * @param {Object} data * @returns {String} * @description render the template with the data source */
function TemplateEngine(template, data) {
return;
}
复制代码
假如我这里有以下模板须要渲染:后端
var template =
"<div>" +
"<p>Name<span><% this.name %></span></p>" +
"<p>Gender<span>" +
"<% if(this.gender === 'male') { %>" +
"Male" +
"<% } else { %> " +
"Female" +
"<% } %>" +
"</span></p>" +
"</div>";
复制代码
数据源:设计模式
var data = {
name: "AJie",
gender: "male"
};
复制代码
须要的渲染结果:数组
<div>
<p>Name<span>AJie</span></p>
<p>Gender<span>Male</span></p>
</div>
复制代码
咱们先将数据源和渲染结果放在一边,先来看看咱们的模板。浏览器
倘若你是浏览器,你会将模板最终渲染成为何样子呢?当你去除掉字符串的引号以及<% %>以后,答案渐渐的就浮出水面了。
<div>
<p>Name<span>this.name</span></p>
<p>Gender<span>
if(this.gender === 'male') {
Male
} else {
Female
}
</span></p>
</div>"
复制代码
咱们如今来分析一下上面这段代码,不难发现,只要是以前没有包含在<% %>之中的模板字符串,都进行原样输出了;而那些再<% %>中的模板字符串则变成了可执行的 JavaScript 逻辑代码。
所以咱们能够先定义一个空数组,用于存储 JavaScript 逻辑代码:
var r = [];
r.push("<div><p>Name<span>");
r.push(this.name);
r.push("</span></p><p>Gender<span>");
if (this.gender === "male") {
r.push(" Male ");
} else {
r.push(" Female ");
}
r.push("</span></p></div>");
return r.join("");
复制代码
对于原样输出的字符串,咱们先将它们封装在 push() 的代码之中,变为 JavaScript 代码,而对于以前的逻辑代码则予以保留。
这么一来,上面的代码就更像是一个函数的函数体了,而调用者正是咱们的 data 数据源。其返回值则是咱们最终须要的结果——渲染好的字符串!
由此咱们能够逐步完善以前的代码:
function TemplateEngine(template, data) {
var code = [];
...
return new Function(code).apply(data);
}
复制代码
值得注意:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
函数的参数首先出现,而函数体在最后。全部参数都写成字符串形式。
apply(thisobj, args)
若是不了解 apply(),还能够查看我以前写的文章:《Function.call()的需求分析》
到了这一步的时候,思路渐渐的明了了起来,咱们只须要在函数内部写出一个生成可执行 JavaScript 代码的字符串数组便可。
这时候咱们就应该想怎么去处理函数内的 template 形参。
有了以前的那些思路铺垫以后,咱们不难发现这是一个字符串检索匹配的过程:
结果前面的分析,对于该模板引擎的设计大体已经 OKay 了!
借助于正则作模式字符串匹配进行功能实现:
/** * @param {String} template * @param {Object} data * @returns {String} * @description render the template with the data source */
function TemplateEngine(template, data) {
var re = /<%([^%>]+)?%>/g,
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, //用于匹配语句标识
code = "var r=[];\n",
cursor = 0,
match;
var add = function(line, js) {
js
? (code += line.match(reExp) ? line + "\n" : "r.push(" + line + ");\n")
: (code +=
line != "" ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : "");
return add;
};
while ((match = re.exec(template))) {
add(template.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
add(template.substr(cursor, template.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, "")).apply(data);
}
复制代码
第一个正则会匹配<%%>
而后把<%
和%>
之间的内容保存下来,第二个正则正好会处理第一个正则保存下来的内容。在<%
的后面和%>
的前面能够没有空格,也能够有一个空格,好比<%name%>
和<% name %>
应该被认为是同样的,因此为了知足这个需求,前面须要添加一个( )?
,( )
表示匹配一个空格,?
表示前面的重复 0 到 1 次,因此( )?
的意思就是说能够有一个空格,也能够没有。 在while
循环中,首先用第一个re
去匹配(match = re.exec(tpl)
),而后<%
和%>
之间的内容被保存在match[1]
中,而后用re2
去匹配(re2.test(match[1])
)。 注意,re
把<%
和%>
之间的内容所有放在match[1]
中了,因此若是是<%name%>
那么match[1]
中的就是"name"
,可是若是是<% name %>
那么match[1]
中的就是" name "
,因此须要使用( )?
来处理一下空格。
测试代码以下:
console.log(TemplateEngine(template, data));
复制代码
控制台打印输出:
<div>
<p>Name<span>AJie</span></p>
<p>Gender<span>Male</span></p>
</div>
复制代码
咱们始终要记住 MVVM 模式是以数据为驱动的。
所谓模板引擎,简单地说,就是依据页面结构模板和数据源,渲染出真正的页面结构的功能函数。
-EFO-
笔者专门在 github 上建立了一个仓库,用于记录平时学习全栈开发中的技巧、难点、易错点,欢迎你们点击下方连接浏览。若是以为还不错,就请给个小星星吧!👍
2019/04/25