doTjs源码研究笔记

首先是入口方法

/*tmpl:模板文本  c:用户自定义配置  def:定义编译时执行的数据*/
doT.template = function(tmpl, c, def) { }

然后进入第一局代码

c = c || doT.templateSettings;

doT.templateSettings包含的代码:

doTjs源码研究笔记
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
        },
doTjs源码研究笔记

先不急着看正则是什么意思,理清思路先,继续往下看代码

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这个函数:

doTjs源码研究笔记
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;
        });
    }
doTjs源码研究笔记

 

这个才一百多行代码的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)

 

未完待续

呵呵呵

doTjs源码研究笔记,布布扣,bubuko.com

doTjs源码研究笔记

上一篇:PHP 文件操作


下一篇:js面向对象编程: js类定义函数时prototype和this区别?