doTjs源码研究笔记
⾸先是⼊⼝⽅法
/*tmpl:模板⽂本  c:⽤户⾃定义配置  def:定义编译时执⾏的数据*/
}
然后进⼊第⼀句代码
c = c || plateSettings;
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,
字符串函数strip的作用
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 : String())
.replace(c.define || skip, function(m, code, assign, value) {//code:def.(\w) assign:':'|'=' value:xxx(.)
if (code.indexOf('def.') === 0) {
code = code.substring(4);//获取def后⾯的部分
}
if (!(code in def)) {
if (assign === ':') {
if (c.defineParams) place(c.defineParams, function(m, param, v) {//如果def后⾯部分没有在传⼊
的属性⾥⾯,则检测value部分
def[code] = {arg: param, text: v};//: xx:xx
});
if (!(code in def)) def[code]= value;//:xx
} else {
new Function("def", "def['"+code+"']=" + value)(def);//否则将value赋值给def
}
}
return '';
})
.replace(c.use || skip, function(m, code) {
if (c.useParams) code = place(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].place(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,做的事情是定义de的code部分,规则总结如下:
    (de:a:b;def[code]={arg:a, text:b}
    (de:a; def[code]=a;
    (de=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+"']" 或者code)运⾏⼀下,并把def作为参数传⼊,结果⽤变量V 存储
    这⾥,我们可以知道
    {{## #}}相当于定义模板,⽽{{# }}相当于解析模板
    如果v为真,则递归调⽤resolveDefs,直到没有上⾯两种标签为⽌,最终,我们把去掉了上⾯两种标签的字符串存储到str变量中
    这个函数可以说理解完了,作⽤是将{{## #}}中模板定义部分提取出来,并放到def对象中,然后解析{{# }},根据def中的定义,将其解析成普通⽂本
 回到template⽅法中
str = ("var out='" + (c.strip ? place(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g,' ')
.replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,''): str)
⼀个逻辑,如果c.trip为假,则直接将该字符串拼接,否则进⾏⼀下字符串替换,两个正则
(1) (^|\r|\n)\t* +| +\t*(\r|\n|$)/g -> ' '
也就是说,所有在空格前后出现了⼀个回车换⾏制表开始或结束符号的,都被替换为⼀个空格
(2) /\r|\n|\t|\/\*[\s\S]*?\*\//g -> ''
这⾥将注释、回车、换⾏、制表符全部替换为空
这样基本把字符串多余的空格去掉了,但是没考虑两个字符之间很多空格的情况?……是假定没⼈这么做么?
继续往下看
.replace(/'|\\/g, '\\$&')
在将去掉空格的字符串与"var out="拼接后,再次执⾏了该字符串替换,作⽤是转义,将单引号反斜杠都加⼀个反斜杠,$&表⽰匹配的字符
.replace(c.interpolate || skip, function(m, code) {
return cse.start + unescape(code) + d;
})
c.interpolate:/\{\{=([\s\S]+?)\}\}/g
⾸先最外层壳{{= }},然后是⼀个捕获项([\s\S]+?)啥都匹配的,形参分别为m代表整体, code代表壳内部的东西
  cse就是那个startend中的⼀个属性,默认是cse.append
  看看unescape
function unescape(code) {
place(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, ' ');
}
(1)./\\('|\\)/g:⾸先匹配\,然后匹配捕获项'或者\,$1代表第⼀个捕获项,也就是说把转义符去掉了
(2)./[\r\t\n]/g:换⾏符制表符回车符,全部变成⼀个空格
  那么最终匿名函数内部返回的是'+(反转义并且去掉多于回车换⾏制表符的code)+',注意上⾯最开始去掉的是后⾯带有多个空格的回车换⾏制表
.de || skip, function(m, code) {
needhtmlencode = true;
return cse.start + unescape(code) + dencode;
})
  匿名函数内部,⾸先将needhtmlencode赋值为true,然后返回的内容是'+(反转义并且去掉多于回车换⾏制表符的
code||'').toString().encodeHTML()+'
.ditional || skip, function(m, elsecase, code) {
return elsecase ?
(code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") :
(code ? "';if(" + unescape(code) + "){out+='" : "';}out+='");
})
  函数内部:如果第⼀个捕获项存在,code存在返回:"';}else if("反转义并且去掉多于回车换⾏制表符的code"){out+='",code不存在返回:"';}else{out+='"
      如果第⼀个捕获想不存在,code存在返回:"';if("反转义并且去掉多于回车换⾏制表符的code"){out+='",code不存在返回:"';}out+='"
.replace(c.iterate || skip, function(m, iterate, vname, iname) {
if (!iterate) return "';} } out+='";
sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate);
return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+"<l"+sid+"){"
+vname+"=arr"+sid+"["+indv+"+=1];out+='";
})
c.iterate:/\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g
{{~ }},去掉两边空格,
(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\}),⾮捕获项,⾸先匹配}}或者空格前的随机内容作为第⼀个捕获项,去掉空格,匹配:,去掉空格,匹配\w和$座位第⼆个捕获项,去掉空格,:,去掉空格,捕获\w和$作为第三个捕获项,去掉空格
  匿名函数内部,如果第⼀个捕获项不存在(即出现{{~\s*}})的情况,则返回"';} } out+='",
  sid⾃增,indv赋值为第三个匹配项或者'i'+sid,将第⼀个捕获项反转义,返回"';var arr"+sid+"="+第⼀个捕获项+";if(arr"+sid+"){var "+第⼆个捕获项+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+"<l"+sid+"){"+vname+"=arr"+sid+"["+indv+"+=1];out+='"
  其实这⾥就是匹配{{~xx:xx[:xx]}}的情况,第三个xx没必要
.replace(c.evaluate || skip, function(m, code) {
return "';" + unescape(code) + "out+='";
}
c.evaluate:/\{\{([\s\S]+?(\}?)+)\}\}/g,{{}},捕获项为随意匹配,中间(\}?)+是为避免后⾯出现多余}的情况,返回"';" + unescape(code) +
"out+='"
+ "';return out;")
将上⾯的内容都替换完后,将内容加上"';return out;"
总结⼀下,传⼊的内容中,⾸先解析的是{{## #}}和{{# }}这两种形式,作⽤如下
{{## #}}
(de:a:b;def[code]={arg:a, text:b},其中text可为表达式,⽽a为参数
(de:a; def[code]=a;
(de=a;(这⾥a是表达式) def[code]=eval(a);
{{# }}
(1){{#[^\de:param}}; 将def对象中的def[code]取出来,param作为其参数,运⾏def[code].text
(2){{#[^\de}};直接获取def中的code中的内容
然后定义str = "var out = '" + 去掉多余空格的字符串,并转义
{{=code}} => '+( unescape(code) )+'
{{!code}} => '+(unescape(code)||'').toString().encodeHTML()+' 同时 needhtmlencode = true
{{? }}
(1){{??code}} => ';}else if("unescape(code)"){out+='
(2){{??}} => ';}else{out+='
(3){{?code}} => ';if("unescape(code)"){out+='
(4){{?}} => ';}out+='
{{~}}
(1){{~}} => ';}} out+='
(2){{~a:b[:c]}} => "';var arr"+sid+"="+ a +";if(arr"+sid+"){var "+b+","+c+"=-1,l"+sid+"=arr"+sid+".length-1;while("+c+"<l"+sid+") {"+b+"=arr"+sid+"["+c+"+=1];out+='"
{{code}} => "';" + unescape(code) + "out+='"
+ "';return out;")
.replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r')
.replace(/(\s|;|\}|^|\{)out\+='';/g, '$1').replace(/\+''/g, '')
.replace(/(\s|;|\}|^|\{)out\+=''\+/g,'$1out+=');
(1)将\n,\r,\t转义
(2)去掉+''和out+='';的情况
(3)out+=''+ 替换为 out+=
if (needhtmlencode && c.selfcontained) {
str = "deHTML=(" + String() + "());" + str;
}
如果存在{{!}}以及c.selfcontained为true,则在String的原型上添加encodeHTML
function encodeHTMLSource() {
var encodeHTMLRules = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' },
matchHTML = /&(?!#?\w+;)|<|>|"|'|\//g;
return function() {
return this ? place(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : this;
};
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。