JS模板引擎:基于字符串拼接
目的
编写一个基于字符串拼接的js模板引擎雏形,这里并不会提供任何模板与数据的绑定。
基本原理
Javascript中创建函数的方式有多种,包括:
1. var func = function () {...}
2. function func () {...}
3. var func = new Function (...);
其中new Function的方法给到了我们很大的自由度,比如:
var func = new Function('a', 'b', 'return a+b;'); func(1, 2); // 输出: 3
这样我们就可以通过字符串来创建函数。
如果我们有模板:
<div>
{{data.name}}
</div>
那么我们希望它达成的作用是:
function func (data) {
return '<div>'+data.name+'</div>';
}
为了让这个转换变得更简单,我们实际上将得到的是:
function func (data) {
var arr = []; // 注意一下这一段, 与模板进行对比
arr.push('<div>');
arr.push(data.name);
arr.push('</div>');
return arr.join('');
}
接下来我们的目标就很明确了:
我们要利用new Function的方式,完成模板到目标函数之间的转换。
使用变量
观察目标函数与模板的区别,总结之后我们得到了如下的代码
function Templer (str) {
return new Function ('data',
'var arr = []; arr.push("'
+str.replace(/[\r\t\n]/g, ' ') // 在代码中去掉换行符
.replace(/{{/g, '");arr.push(') // 替换变量的开始部分
.replace(/}}/g, ');arr.push("') // 替换变量的结尾部分
+'");return arr.join("");'
);
}
于是我们可以这样使用:
var func = Templer('<div>{{data.name}}</div>');
/* 输出:
function anonymous(data) {
var arr = []; arr.push("<div>");arr.push(data.name);arr.push("</div>");return arr.join("");
}
*/
func({name: '笨笨'});
// 输出:"<div>笨笨</div>"
模板中的逻辑
虽然我们完成了最基本的变量替换,但显然功能还过于单一。接下来我们来做第二步:在模板中嵌入逻辑(js代码)。
由于我们所做的就是将模板处理成正确的js代码再使用new Function来生成函数,因此将js代码嵌入到我们的模板引擎中也显得非常轻松自然。
观察比较一下模板和目标函数:
function func (data) {
var arr = [];
arr.push("<div>");
// for的逻辑
for (var i=0; i<data.items.length;i++) {
arr.push("<div>");
arr.push(data.items[i].name);
arr.push("</div>");
}
arr.push("</div>");
return arr.join('');
}
<div>
{% for (var i=0; i<data.items.length;i++) { %}
<div>{{data.items[i].name}}</div>
{% } %}
</div>
得到以下的结果:
function Templer (str) {
return new Function ('data',
'var arr = []; arr.push("'
+str.replace(/[\r\t\n]/g, ' ') // 在代码中去掉换行符
.replace(/{{/g, '");arr.push(') // 替换变量的开始部分
.replace(/}}/g, ');arr.push("') // 替换变量的结尾部分
.replace(/{% (.+?) %}/g, '");$1arr.push("') // 替换js部分
+'");return arr.join("");'
);
}
*注:使用数组拼接的方式是由于早期的js中数组拼接的效率要优于字符串拼接,因此我们也可以这样来实现模板:
function Templer (str) {
return new Function ('data',
'var str =""; str += "'
+str.replace(/[\r\t\n]/g, ' ') // 在代码中去掉换行符
.replace(/{{/g, '";str += ') // 替换变量的开始部分
.replace(/}}/g, ';str += "') // 替换变量的结尾部分
.replace(/{% (.+?) %}/g, '";$1 str += "') // 替换js部分
+'";return str;'
);
}
测试一下:
var func = Templer(tplStr);
/* tplStr:
<div>{{data.name}}</div>
<div>家庭成员</div>
{% for (var i=0, member; member = data.members[i], member; i++) { %}
<div>{{i+1}}-名称:{{member}}</div>
{% } %}
*/
func(({name: '笨笨', members: ['宝宝','小宝宝','小笨笨','笨笨']}));
模板的扩展
最后我们来做一些扩展,包括引入 for ... in ...的语法和include
直接上代码:
(function (g) {
function Templer(name, tpl) {
if (!Templer._cached[name]) { // 用name保存建立过的模板函数
Templer._cached[name] = new Function('data', 'var arr = []; (function (d) { arr.push("'
+ tpl
.replace(lineBreakRegex, lineBreakReplace) // 替换换行符
.replace(jsRegex, jsReplace) // 替换js
.replace(variableStartRegex, variableStartReplace) // 替换变量起始
.replace(variableEndRegex, variableEndReplace) // 替换变量终止
+'");})(data); return arr.join("");');
}
return Templer._cached[name];
}
Templer._cached = {};
var variableStartRegex = /{{/g,
variableStartReplace = '");arr.push(',
variableEndRegex = /}}/g,
variableEndReplace = ');arr.push("',
lineBreakRegex = /[\r\t\n]/g,
lineBreakReplace = ' ',
jsRegex = /{% (.+?) %}/g,
jsReplace = function () {
var str = arguments[1], stop = false;
// 开始处理特殊格式
if (str === 'end') {
stop = true;
str = '}';
}));
// 处理 for item in items 的逻辑,替换为可执行的代码
stop || (str = str.replace(startForRegex, function () {
stop = true;
var a = arguments[1],
b = arguments[2],
i = nextVar();
return 'for (var '+i+' = 0, '+a+'; '+a+' = '+b+'['+i+']; '+i+'++) {';
}));
// 处理 include 的格式,通过调用Templer(tId)的形式替换
stop || (str = str.replace(includeRegex, function () {
stop = true;
var tplName = arguments[1];
return 'arr.push(Templer("'+tplName+'")(d));';
}));
// 结束处理特殊格式
return '");'+str+'arr.push("';
},
startForRegex = /^for (.+?) in (.+?)$/g,
includeRegex = /^include (.+?)$/g,
nextId = 0,
nextVar = function () {
return '_' + (nextId++).toString(36);
};
window.Templer = Templer;
})(window);
测试一下:
<div id="div1"></div><script type="text/template" id="tpl2">
<h4>Pets:</h4>
<ol>
{% for pet in d.pets %}
<li><span>{{pet.name}}</span>
<ul>
{% for food in pet.food %}
{% if (food === 'banana') { %}
<li>balala~</li>
{% } else { %}
<li>{{food}}</li>
{% } %}
{% end %}
</ul>
</li>
{% end %}
</ol>
</script>
<script type="text/template" id="tpl1">
<p>{{d.name}} - {{d.info.age}}</p>
{% if (d.info.age > 20) { %}
<div>age is 20+</div>
{% } else { %}
<div>age is 20-</div>
{% } %}
<p>
{% include tpl2 %}
</div>
</script>
<script type="text/javascript" src="templer.js"></script>
<script type="text/javascript">
Templer('tpl2', document.getElementById('tpl2').innerHTML);
document.getElementById('div1').innerHTML = Templer('tpl1', document.getElementById('tpl1').innerHTML)({
name: 'Miao',
info: {age: 27},
pets: [{
name: 'Meow',
food: ['apple', 'banana']
}, {
name: 'Doge',
food: ['apple', 'banana']
}]
});
</script>
总之就是正则+替换,这里只是提供一个思路和雏形,知道了基本的做法其他的只要发挥想象就行了。
预编译
既然我们是将模板字符串处理成new Function要使用的函数体字符串,我们就可以利用node或其他工具将模板预编译成js文件中的函数,免去即时编译的耗费。
// 模板引擎的做法
new Function ('data', 函数体);
// 预编译到js文件中
Template[tplName] = function (data) {
函数体
}
小结
通过使用正则替换处理模板字符串来得到new Function的函数体,从而将模板转换为模板函数。由于只是基于字符串的处理和拼接,因此这样的引擎在web端和server端都是可用的,当然这种方式也不会提供任何数据绑定的功能。
扩展:ES6的Template String
ES6中定义了Template String的规范,在语言/浏览器层面支持了基于字符串的Template。
通过Template String我们可以进行变量替换:
var a = 10;
var b = 5;
console.log(`${a} + ${b} = ${a+b}`);
当然也可以使用函数:
function getName() { return "benben"; }
console.log(`大家好,我是${getName().toUpperCase()}~`);
以及可以换行:
console.log(`大家好, 我是笨笨`);
还有tagged templates,相当于调用一个函数来处理模板:
fn`Hello ${you}! You're looking ${adjective} today!`
function fn (arr, ...tokens) {
//
}
/*
相当于调用:
fn(["Hello ", "! You're looking ", " today!"], you, adjective);
*/
JS模板引擎:基于字符串拼接的更多相关文章
- 前后端数据交互处理基于原生JS模板引擎开发
json数据错误处理,把json文件数据复制到----> https://www.bejson.com/ 在线解析json 这样能直观的了解到是否是json数据写错,在控制台打断点,那里错误打那 ...
- 简易js模板引擎
前面 js 模板引擎有很多很多,我以前经常用 art-template ,有时候也会拿 vue 来当模板引擎用. 直到...... 年初的时候,我还在上个项目组,那时候代码规范是未经允许不能使用 [外 ...
- 为什么要使用JS模板引擎
我之前在写一个输入联想控件的时候,改过好几个版本,每个版本不是因为性能不好就是因为代码凌乱而被推翻,最后用了understore模板引擎,效果有明显改善.整好这两天在研究互联网技术架构,发现很多的开发 ...
- JS模板引擎:tppl
全球最快的JS模板引擎:tppl 废话不多说,先上测试: 亲测请访问:[在线测试地址]单次结果不一定准确,请多测几次. tppl 的编译渲染速度是著名的 jQuery 作者 John Resig 开发 ...
- vue系列---Mustache.js模板引擎介绍及源码解析(十)
mustache.js(3.0.0版本) 是一个javascript前端模板引擎.官方文档(https://github.com/janl/mustache.js) 根据官方介绍:Mustache可以 ...
- doT js 模板引擎【初探】要优雅不要污
js中拼接html,总是感觉不够优雅,本着要优雅不要污,决定尝试js模板引擎. JavaScript 模板引擎 JavaScript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注. ...
- js模板引擎介绍搜集
js模板引擎越来越多的得到应用,如今已经出现了几十种js模板引擎,国内各大互联网公司也都开发了自己的js模板引擎(淘宝的kissy template,腾讯的artTemplate,百度的baiduTe ...
- js模板引擎--artTemplate
js模板引擎--artTemplate 以前研究过一段时间的handlebars,但因为其渲染性能略逊于腾讯的artTemplate(在artTemplate的GitHub官网上有推荐的性能测试地址) ...
- 百度JS模板引擎 baiduTemplate 1.0.6 版
A.baiduTemplate 简介 0.baiduTemplate希望创造一个用户觉得“简单好用”的JS模板引擎 注:等不及可以直接点左侧导航中的”C.使用举例“,demo即刻试用. 1.应用场景: ...
随机推荐
- NDK常用命令
NDK Build 用法(NDK Build) 1.ndk-build的用法 Android NDKr4引入了一个新的.小巧的shell脚本ndk-build,来简化源码编译. 该文件位于NDK根 ...
- apk反汇编之smali语法
类型 Dalvik的字节码中拥有两个主要的类型:基类和引用类型.引用类型 引用类型是对象和数组,其他的一切都是基类 基类被一个简单的字符描述.我没有提出这些缩写词———他们实际以字符串的形式存储于 ...
- (转)Hadoop MapReduce链式实践--ChainReducer
版本:CDH5.0.0,HDFS:2.3.0,Mapreduce:2.3.0,Yarn:2.3.0. 场景描述:求一组数据中按照不同类别的最大值,比如,如下的数据: data1: A,10 A,11 ...
- 标签(改变样式style)
id可以换为class,class对应的名字可以多个一样 <div class="box">box1</div> <div class="b ...
- Mac系统下安装Tomcat,以及终端出现No such file or directory的错误提示解决方案
Tomcat,作为一个免费的服务器口碑实在太好,本想安装一个研究研究,无奈电脑是mac系统,在网上搜了一些安装方法总是出错,直到遇到了这篇博客,http://www.cnblogs.com/qingy ...
- Xamarin For Visual Studio 3.0.54.0 完整离线破解版
Xamarin For Visual Studio 3.0.54.0 完整离线破解版 Xamarin For Visual Studio就是原本的Xamarin For Android 以及 Xama ...
- opencart配置
1.安装opencart 2.修改后台目录(慎重,修改后插件安装会出错) Opencart默认的后台是网站/admin这样子,很多人可以猜到这种组合对于正式生产环境很不安全,我们可以把这个admin改 ...
- vmware中的bridge、nat、host-only的区别
概述: VMWare提供了三种工作模式,它们是bridged(桥接模式).NAT(网络地址转换模式)和host-only(主机模式).要想在网络管理和维护中合理应用它们,你就应该先了解一下这三种工作模 ...
- openstack controller ha测试环境搭建记录(五)——配置rabbitmq集群
配置rabbitmq集群的步骤非常简单,因为其本身含集群功能,参考openstack官网文档:http://docs.openstack.org/ha-guide/controller-ha-rabb ...
- java中try 与catch的使用
(2011-10-08 17:08:43) 转载▼ 标签: 杂谈 分类: Java try{//代码区}catch(Exception e){//异常处理}代码区如果有错误,就会返回所写异常的处理. ...