首页 > 代码库 > doTjs源码研究笔记
doTjs源码研究笔记
首先是入口方法
/*tmpl:模板文本 c:用户自定义配置 def:定义编译时执行的数据*/
doT.template = function(tmpl, c, def) { }
然后进入第一局代码
c = c || doT.templateSettings;
doT.templateSettings包含的代码:
templateSettings: { evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g, interpolate: /\{\{=([\s\S]+?)\}\}/g, encode: /\{\{!([\s\S]+?)\}\}/g, use: /\{\{#([\s\S]+?)\}\}/g, useParams: /(^|[^\w$])def(?:\.|\[[\‘\"])([\w$\.]+)(?:[\‘\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\‘[^\‘]+\‘|\{[^\}]+\})/g, define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g, defineParams:/^\s*([\w$]+):([\s\S]+)/,//xxx(\w):xxx(.) conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g, iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g, varname: ‘it‘, strip: true, append: true, selfcontained: false },
先不急着看正则是什么意思,理清思路先,继续往下看代码
var cse = c.append ? startend.append : startend.split,
这里定义了一个叫cse的变量,如果c.append为true,则它的值是startend.append,否则它的值是startend.split,看看startend是什么
var startend = { append: { start: "‘+(", end: ")+‘", endencode: "||‘‘).toString().encodeHTML()+‘" }, split: { start: "‘;out+=(", end: ");out+=‘", endencode: "||‘‘).toString().encodeHTML();out+=‘"} },
OK,先不管它的作用,接着往下看
needhtmlencode, sid = 0, indv,
str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl;
在定义了cse这个变量后,相继又定义了needhtmlencode,sid,indv,str,其中str又牵涉到了resolveDefs这个函数:
function resolveDefs(c, block, def) { return ((typeof block === ‘string‘) ? block : block.toString()) .replace(c.define || skip, function(m, code, assign, value) {//code:def.def.def.xxx(\w) assign:‘:‘|‘=‘ value:xxx(.) if (code.indexOf(‘def.‘) === 0) { code = code.substring(4);//获取def后面的部分 } if (!(code in def)) { if (assign === ‘:‘) { if (c.defineParams) value.replace(c.defineParams, function(m, param, v) {//如果def后面部分没有在传入的属性里面,则检测value部分 def[code] = {arg: param, text: v};//def.xx: xx:xx }); if (!(code in def)) def[code]= value;//def.xx:xx } else { new Function("def", "def[‘"+code+"‘]=" + value)(def);//否则将value赋值给def } } return ‘‘; }) .replace(c.use || skip, function(m, code) { if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) { if (def[d] && def[d].arg && param) { var rw = (d+":"+param).replace(/‘|\\/g, ‘_‘); def.__exp = def.__exp || {}; def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2"); return s + "def.__exp[‘"+rw+"‘]"; } }); var v = new Function("def", "return " + code)(def); return v ? resolveDefs(c, v, def) : v; }); }
这个才一百多行代码的doT文件还真是耐嚼啊,分析一下这个函数的作用
1.首先将block转化为string类型
2.调用字符串替换方法,正则为c.define || skip,分析一下c.define这个正则
c.define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g
最外层匹配内容为{{##一些字符串#}},里面的内容依次是,\s*去掉一些乱打的空格,第一个捕获项([\w\.$]+),匹配abc123_.abc123_.这种形式的内容,然后\s*去掉一些空格,第二个捕获项(\:|=),匹配‘:‘或者‘=‘,第三个捕获项([\s\S]+?)什么都匹配
得出结论,c.define匹配的内容是{{##(数字字母下划线和点组成的随意组合)(:|=)(乱起八糟的一些东西)#}},三个括号分别代表不同的匹配项,
再看看匿名函数的形参,m对应的是被匹配的整个内容,code,assign,value分别代表三个不同的捕获项
还有个skip:/$^/;结束后马上开始?只有空字符才这样吧……意思是什么都不匹配,什么都没匹配意思是跳过了
3.if语句,如果code是以def.开头,则将def.后面的内容截取下来,赋值给code
4.if语句,如果code在def中没有定义,则继续执行
如果assign部分是‘:‘的话继续执行
如果c.defineParams有定义,则将value部分进行字符串替换,正则是c.defineParams
c.defineParams:/^\s*([\w$]+):([\s\S]+)/
^\s*去掉开始的空格,([\w$]+)第一个捕获项,匹配多个数字字母下划线,然后碰到:,([\s\S]+)第二个捕获项,什么都匹配
匹配类(数字字母下划线):(乱起八糟的一些东西),匿名函数的形参m代表整个匹配项,param代表第一个捕获项,v代表第二个捕获项
匿名函数内部定义code,内容为{arg: param, text: v};
如果经过上面的代码code依然在def中没有定义,则将value作为值,在def中定义code
否则(如果assign是‘=‘)
调用,new Function("def", "def[‘"+code+"‘]=" + value)(def);直接将value赋值给def[code],为什么用构造函数?因为这样value就能被求出来啦,就跟eval一样
总结一下,从2到4,做的事情是定义def.code的code部分,规则总结如下:
(1)def.code:a:b;def[code]={arg:a, text:b}
(2)def.code:a; def[code]=a;
(3)def.code=a;(这里a是表达式) def[code]=eval(a);
如果def中本就有code这个属性,那么上述三个过程都不会发生
5.经过上面的步骤,已经解析了{{## #}}了,并且都已经被替换为空,def对象中也会有相应属性
继续进行字符串替换,正则是c.use || skip,skip不用说了,直接看c.use
c.use:/\{\{#([\s\S]+?)\}\}/g
外壳:{{# }},([\s\S]+?),捕获项,而且啥都行,对应匿名函数参数,m是整个匹配项,code代表第一个捕获项,
if语句,如果c.useParams存在的话,对code进行字符串替换,并且正则就是c.useParams
c.useParams:/(^|[^\w$])def(?:\.|\[[\‘\"])([\w$\.]+)(?:[\‘\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\‘[^\‘]+\‘|\{[^\}]+\})/g,
长啊……
(^|[^\w$])第一个捕获项,开始字符或者非数字字母下划线结尾符,
def,
(?:\.|\[[\‘\"])非捕获项,匹配.或者[‘或者[",
第二个捕获项([\w$\.]+),匹配abc123_.abc123_.abc123_.,
(?:[\‘\"]\])?,匹配‘]或者"],非贪婪匹配
\s*\:\s*,去掉空格,匹配:,
([\w$\.]+|\"[^\"]+\"|\‘[^\‘]+\‘|\{[^\}]+\}),第三个捕获项,匹配abc123_.abc123_.或者"xxxxxx"或者‘xxxxxx‘,或者{xxxxxxx}
整个正则代表: (一些可能的特殊字符)def.(数字字母下划线和点的组合)[或者用[‘‘]|["包起来"]]:(数字字母下划线和点的组合|"乱七八糟"|‘乱七八糟‘|{乱七八糟})
m,s,d,param分别对应整个匹配项和三个捕获项
if语句,如果def[d]和def[d].arg和param都存在的话,拼接为d:param,并将其中的‘和\都替换为_,赋值给rw变量
定义def.__exp[rw]为将def[d].text中的 特殊字符+def[d].arg+特殊字符 替换为 特殊字符+param+特殊字符 的形式
返回s + "def.__exp[‘"+rw+"‘]"
小结:def.d:param中,将d.text中符合arg的部分替换为param,并将d:param(\和‘替换为_)作为唯一标识rw,赋值给def.__exp,返回s + "def.__exp[‘"+rw+"‘]"并将def.d:param替换,如果def[d]不存在的话就会被替换为undefined,
然后把已经解析完{{# }}形式的code(实际上是s + "def.__exp[‘"+rw+"‘]")运行一下,并把def作为参数传入,结果用变量V存储
这里,我们可以知道
{{## #}}相当于定义模板,而{{# }}相当于解析模板
如果v为真,则递归调用resolveDefs,直到没有上面两种标签为止,最终,我们把去掉了上面两种标签的字符串存储到str变量中
这个函数可以说理解完了,作用是将{{## #}}中模板定义部分提取出来,并放到def对象中,然后解析{{# }},根据def中的定义,将其解析成普通文本
回到template方法中
str = ("var out=‘" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g,‘ ‘) .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,‘‘): str)
未完待续
呵呵呵