function buildTmplFn( markup ) { Function("jQuery","$item", // Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10). "var $=jQuery,call,__=[],$data=$item.data;" + // Introduce the data as local variables using with(){} "with($data){__.push('" + // Convert the template into pure JavaScript jQuery.trim(markup) .replace( /([\\'])/g, "\\$1" )//将\或者'前面都添加一个转义符\ .replace( /[\r\t\n]/g, " " )//将空格符全部转成空字符串 .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" )//将类似${name}这种写法的转成{{=name}},换句话说,在页面script中也可以使用${name}来赋值,这里都会统一转成{{=name}}格式 .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, ( all, slash, type, fnargs, target, parens, args ) { /* * type表示你具体需要显示的文本功能,我们这个例子是=,表示仅仅是显示 * */ var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; "Unknown template tag: " + type; } def = tag._default || []; if ( parens && !/\w$/.test(target)) { target += parens;//拼接主干信息 parens = ""; } ( target ) { target = unescape( target );//去转义符 args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target; exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; } else { exprAutoFnDetect = expr = def.$1 || "null"; } fnargs = unescape( fnargs );"');" + tag[ slash ? "close" : "open" ] .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" )//这种方法可以学习一下,先使用占位符站住你需要替换的信息,然后使用split分隔开成数组,再使用join方法加入参数合成字符串,在数组中join的效率还是不错的 .split( "$1a" ).join( exprAutoFnDetect )//将之前拼接好的字符串替换占位符$1a .split( "$1" ).join( expr )//替换$1 .split( "$2" ).join( fnargs || def.$2 || "" ) +//依旧是替换 "__.push('"; }) + "');}return __;" ); }
其实这个方法的作用就是根据内置正则表达式,解析模版字符串,截取相应的数据,拼凑成一个以后使用的匿名函数。这个匿名函数的功能主要将我们之后传入的数据源users根据正则解析,加入到模版字符串中。既然正则是这个方法的核心,那我们就来看一下这些正则,前几个正则比较简单,最后一个正则比较复杂,我们将它做拆解来理解。
/* * \{\{ --匹配{{ * (\/?) --优先匹配/,捕捉匹配结果 ($1)slash * (\w+|.) --优先匹配字符,捕获匹配结果 ($2)type * (?: --匹配但不捕获 * \( --匹配( * ( --捕获匹配结果 ($3)fnargs * (?: --匹配但不捕捉 * [^\}]|\} --优先匹配非},如果有},要求匹配这个}后面不能再出现} * (?!\}) --否定顺序环视,不能存在} * )*? --非优先匹配设定,尽可能少的去匹配 * )? --优先匹配 * \) --匹配) * )? --优先匹配 * (?: --匹配但不捕捉 * \s+ --优先匹配,匹配空格符,至少一个 * (.*?)? --非优先设定,尽可能少的去匹配,但必须要尽量尝试。 ($4)target * )? --优先匹配 * ( --捕获匹配结果 ($5)parens * \( --匹配( * ( --捕获匹配结果 ($6)args * (?: --匹配但不捕获 * [^\}]|\} --优先匹配非},如果有},要求匹配这个}后面不能再出现} * (?!\}) --否定顺序环视,不能存在} * )*? --非优先匹配设定,尽可能少的去匹配 * ) * \) --匹配) * )? --优先匹配 * \s* --优先匹配,空白符 * \}\} --匹配}} * /g --全局匹配 * *
因为replace的解析函数中一共有7个参数,除了第一个参数表示全部匹配外,其他都是分组内的匹配。我在注释中都一一列出,方便我们阅读。观察一下正则,我们可以了解这个插件给与我们的一些语法使用,比如说:
页面模版内可以这样写:
${name} {{= name}}
这两种写法都是对的,为什么前一条正则就是将${name}转成{{= name}},另外为什么=与name之间需要有空格呢?其实答案在正则里,看一下($4)target匹配的前一段是\s+,这表明必须至少要匹配一个空格符。先将我们缩写的格式转成{{= xx}}再根据(.*?)?查找出xx的内容,也就是name,其实正则的匹配过程并不是像我所说的这样,在js中的正则在量词的出现时,会进行优先匹配,然后再慢慢回溯,我这样只是形象的简单说一下。对于这条正则,我们在后续的API中继续延伸。