简易js模板引擎
前面
js 模板引擎有很多很多,我以前经常用 art-template ,有时候也会拿 vue 来当模板引擎用。
直到......
年初的时候,我还在上个项目组,那时候代码规范是未经允许不能使用 【外部代码】,囧 。
有了需求,那么就去写吧,但是后来因为一些原因没用上。后来分了产线,自己搭了一套构建,用了几个月感觉挺爽,把这小段代码按照比较大众的规范重写,跟大家分享下。
https://github.com/shalldie/mini-tpl
语法
首先是选择模板语法,ejs语法是首选,因为大众,更无需去学习指令型模板引擎的那些东西。
如果写过 jsp 或者 asp/asp.net 的可以直接上手。
怎么用它?
我要这么用
<body>
<div id="root"></div>
<script id="tplContent" type="text/html">
<ul>
<% for(var i=0; i<data.length; i++){
var item = data[i];
if(item.age < 30){%>
<li>我的名字是<%=item.name%>,我的年龄是<%=item.age%></li>
<%}else{%>
<li>my name is <%=item.name%>,my age is a sercet.</li>
<%}%>
<% } %>
</ul>
</script>
<script src="../build/mini-tpl.min.js"></script>
<script>
var data = [{ name: 'tom', age: 12 }, { name: 'lily', age: 24 }, { name: 'lucy', age: 55 }];
var content = document.getElementById('tplContent').innerHTML;
var result = miniTpl(content, data);
document.getElementById('root').innerHTML = result;
</script>
</body>
想要这么用,那么就分析一下怎么才能实现。
new Function
1 const content = 'console.log("hello world");';
let func = new Function(content);
func(); // hello world
new Function ([arg1[, arg2[, ...argN]],] functionBody)
functionBody 一个含有包括函数定义的JavaScript语句的字符串。
使用Function构造器生成的函数,并不会在创建它们的上下文中创建闭包;它们一般在全局作用域中被创建。
当运行这些函数的时候,它们只能访问自己的本地变量和全局变量,不能访问Function构造器被调用生成的上下文的作用域。(MDN)
也就是说:
- 可以用 new Function 来动态的创建一个函数,去执行某动态生成的函数定义js语句。
- 通过 new Function 生成的函数,作用域在全局。
- 那么传参有3种:
把变量放到全局(扯淡)、函数传参、用call/apply把值传给函数的this。
最初我用的是 call 来传值,如今想了想不太优雅,换成了用参数传递。也就是这样:
const content = 'console.log(data);';
let func = new Function('data', content);
func('hello world'); // hello world
到此为止,雏形有了。下面来拆分。
模板拆分
先看模板:
<% for(var i=0; i<data.length; i++){
var item = data[i];
if(item.age < 30){%>
<li>我的名字是<%=item.name%>,我的年龄是<%=item.age%></li>
<%}else{%>
<li>my name is <%=item.name%>,my age is a sercet.</li>
<%}%>
<% } %>
js 逻辑部分,由 <%%> 包裹, js 变量的占位,由 <%= %> 包裹,剩下的是普通的要拼接的html字符串部分。
也就是说,需要用正则找出的部分有3种:
<%%>逻辑部分的js内容<%=%>占位部分的js内容- 其它的
纯文本内容
其中第2项,js占位的部分,也属于拼接文本。所以可以放在一起,就是 js部分 ,拼接部分。
正则提取
当然是选择正则表达式啊!
这里先跟大家扩展一下关于伪数组方面的内容,以及浏览器的控制台如何看待伪数组:

不扯远,直接说结论:
只要有 int类型的 length属性,有 function类型 的 splice属性。 那么浏览器就会认为他是一个数组。
如果里面的其它属性按照索引来排序,甚至还可以像数组里面的项那样在控制台展示出来。
这种判断方式叫 duck typing ,如果一个东西长得像鸭子,而且叫起来像鸭子,,,那么它就是鸭子 0_o
回到正文,这个需要多次从模板中,把 js逻辑部分 和 文本 依次提取出来。
对于每一次提取,都要获取提取出的内容,本次匹配最后的索引项(用于提起文本内容)。所以我选择了 RegExp.prototype.exec 。

举个例子,RegExp.prototype.exec 返回的是一个集合(伪数组),它的类型是这样的:
| 属性/索引 | 描述 |
|---|---|
[0] |
匹配的全部字符串 |
[1],...[n] |
括号中的分组捕获 |
index |
匹配到的字符位于原始字符串的基于0的索引值 |
input |
原始字符串 |
通过这样,就可以拿到匹配到的 js 逻辑部分,并通过 index 和本次匹配到的内容,来获取每个js逻辑部分之间的文本内容项。
要注意,在全局匹配模式下,正则表达式会接着上次匹配的结果继续匹配新的字符串。
/**
* 从原始模板中提取 文本/js 部分
*
* @param {string} content
* @returns {Array<{type:number,txt:string}>}
*/
function transform(content) {
var arr = []; //返回的数组,用于保存匹配结果
var reg = /<%(?!=)([\s\S]*?)%>/g; //用于匹配js代码的正则
var match; //当前匹配到的match
var nowIndex = 0; //当前匹配到的索引 while (match = reg.exec(content)) {
// 保存当前匹配项之前的普通文本/占位
appendTxt(arr, content.substring(nowIndex, match.index));
//保存当前匹配项
arr.push({
type: 1, //js代码
txt: match[1] //匹配到的内容
});
//更新当前匹配索引
nowIndex = match.index + match[0].length;
}
//保存文本尾部
appendTxt(arr, content.substr(nowIndex));
return arr;
} /**
* 普通文本添加到数组,对换行部分进行转义
*
* @param {Array<{type:number,txt:string}>} list
* @param {string} content
*/
function appendTxt(list, content) {
content = content.replace(/\r?\n/g, "\\n");
list.push({ txt: content });
}
...
得到了js逻辑项 和 文本内容 ,就可以把他们拼在一起,来动态生成一个function。要注意的是,文本内容中,包含 js占位项,这个地方要转换一下。
/**
* 模板 + 数据 =》 渲染后的字符串
*
* @param {string} content 模板
* @param {any} data 数据
* @returns 渲染后的字符串
*/
function render(content, data) {
data = data || {};
var list = ['var tpl = "";'];
var codeArr = transform(content); // 代码分割项数组 for (var i = 0, len = codeArr.length; i < len; i++) {
var item = codeArr[i]; // 当前分割项 // 如果是文本类型,或者js占位项
if (!item.type) {
var txt = 'tpl+="' +
item.txt.replace(/<%=(.*?)%>/g, function (g0, g1) {
return '"+' + g1 + '+"';
}) + '"';
list.push(txt);
}
else { // 如果是js代码
list.push(item.txt);
}
}
list.push('return tpl;'); return new Function('data', list.join('\n'))(data);
}
这样就完成了简易的模板引擎,不要觉得拼字符串慢。
在现代浏览器(IE8开始)中,特地对字符串的操作做了大量的优化,用 += 拼字符串,要比用数组 push 再 join 的方式快很多很多,即使放到IE7(IE6不清楚)中,我这里测试也是拼字符串快。。。
最后
模板引擎这东西我搜了一下园子里面有不少,我这是炒炒冷饭。
造轮子这事偶尔为之会提高成就感,但是干什么都要自己造就很caodan了。
附上 github 地址:https://github.com/shalldie/mini-tpl
希望大家钱途无量,少加班,能写喜欢的代码 :D
.
简易js模板引擎的更多相关文章
- JS模板引擎 :ArtTemplate (2)
上一篇初略的介绍了一下javascript中的模板引擎,有兴趣的可以戳 这里 . 这一篇将带着大家一起做一个简易的模板引擎, 上一篇介绍到:模板引擎其实做的就是两件事. 根据一定的规则,解析我们所定义 ...
- doT js 模板引擎【初探】要优雅不要污
js中拼接html,总是感觉不够优雅,本着要优雅不要污,决定尝试js模板引擎. JavaScript 模板引擎 JavaScript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注. ...
- 各种JS模板引擎对比数据(高性能JavaScript模板引擎)
最近做了JS模板引擎测试,拿各个JS模板引擎在不同浏览器上去运行同一程序,下面是模板引擎测试数据:通过测试artTemplate.juicer与doT引擎模板整体性能要有绝对优势: js模板引擎 Ja ...
- JS 模板引擎 BaiduTemplate 和 ArtTemplate 对比及应用
最近做项目用了JS模板引擎渲染HTML,JS模板引擎是在去年做项目是了解到的,但一直没有用,只停留在了解层面,直到这次做项目才用到,JS模板引擎用了两个 BaiduTemplate 和 ArtTemp ...
- 掌握js模板引擎
最近要做一个小项目,不管是使用angularjs还是reactjs,都觉得大材小用了.其实我可能只需要引入一个jquery,但想到jquery对dom的操作,对于早已习惯了双向绑定模式的我,何尝不是一 ...
- js模板引擎
js模板引擎包括如下: template 官方参考:http://aui.github.io/artTemplate BaiduTemplate 官方参考:http://baidufe.github. ...
- 调研js模板引擎
js模板引擎越来越多的得到应用,如今已经出现了几十种js模板引擎,国内各大互联网公司也都开发了自己的js模板引擎(淘宝的kissy template,腾讯的artTemplate,百度的baiduTe ...
- js模板引擎介绍搜集
js模板引擎越来越多的得到应用,如今已经出现了几十种js模板引擎,国内各大互联网公司也都开发了自己的js模板引擎(淘宝的kissy template,腾讯的artTemplate,百度的baiduTe ...
- PHP实现简易的模板引擎
PHP实现简易的模板引擎 1.MVC简介 MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式(详情自己百度): 1. Mode ...
随机推荐
- 集美大学网络1413第十二次作业成绩(个人作业3) -- Alpha阶段个人总结
题目 个人作业3--个人总结(Alpha阶段) 优秀作业链接:**068 未交:**087 个人作业3成绩 学号 姓名 总结(4) 5个问题(2.5) 自我评价(2.5) 博客互动 (1) 总分(10 ...
- 201421123059 http://www.cnblogs.com/deng201421123059/
201421123059 http://www.cnblogs.com/deng201421123059/
- 201521123017 《Java程序设计》第2周学习总结
1. 本章学习总结 (1)Arrays和String的用法及其函数的一些运用,例如sort函数,输入多个数字,用Arrays.sort(数组名),对数组中的元素排序,从小到大,与冒泡法对比更加方便使用 ...
- 201521123089 《Java程序设计》第2周学习总结
1. 本周学习总结 ① java数组的使用.② String类,对象是不可变的,当新的字符串的内容与字符串池中的内容相同时,不是重新开辟新的内存存储,而是共享. 2. 书面作业 (1)使用Eclips ...
- 支付宝支付php的demo或sdk报错 Warning: openssl_sign() [function.openssl-sign]: Unknown signature algorithm. in
最近在做支付宝支付,在本地测试一切正常,上传到服务器就遇到报错: Warning: openssl_sign() [function.openssl-sign]: Unknown signature ...
- java向前引用
根据看书和看得文章,引出了一个关于"向前引用"的问题: public class InstanceInitTest { static { // { a = 6; System.ou ...
- Struts2开山篇【引入Struts、自定义MyStruts框架】
前言 这是Strtus的开山篇,主要是引入struts框架-为什么要引入struts,引入struts的好处是什么-. 为什么要引入struts? 首先,在讲解struts之前,我们来看看我们以前写的 ...
- Hibernate第二篇【API讲解、执行流程图】
前言 从上一篇中已经大致介绍了Hibernate并且有了一个快速入门案例的基础了,-.本博文主要讲解Hibernate API 我们看看快速入门案例的代码用到了什么对象吧,然后一个一个讲解 publi ...
- could not get next sequence value
1.触发事件 在电脑A上敲项目代码,数据库原始资料是直接使用别人写好的sql导入(建表和导入表数据等): 将电脑A上数据库的资料,使用PL/SQL Developer导出项目中所用表(此时未导出Ora ...
- JDBC数据库之添加数据
通过JDBC向数据库中添加数据,可以使用INSERT语句实现插入数据SQL语句,对于SQL语句中的参数可以只用占位符"?"代替,然后通过PreparedStatement对其赋值以 ...