目的
编写一个基于字符串拼接的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 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("");‘ ); }
测试一下:
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; // 开始处理特殊格式 stop || (str = str.replace(‘end for‘, function () { stop = true; return ‘}‘; })); 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+‘++) {‘; })); 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, endForRegex = /^end for$/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 for %} </ul> </li> {% end for %} </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端都是可用的,当然这种方式也不会提供任何数据绑定的功能。
时间: 2024-10-09 18:26:30