(七)jQuery.extend代码段
就过之前对grep、map、merge的准备,现在可以入手flter函数了:
filter: function(t,r,not) { // Figure out if we're doing regular, or inverse, filtering var g = not !== false ? jQuery.grep : function(a,f) {return jQuery.grep(a,f,true);}; while ( t && /^[a-z[({<*:.#]/i.test(t) ) { var p = jQuery.parse; for ( var i = 0; i < p.length; i++ ) { var re = new RegExp( "^" + p[i][0] // Look for a string-like sequence .replace( 'S', "([a-z*_-][a-z0-9_-]*)" ) // Look for something (optionally) enclosed with quotes .replace( 'Q', " *'?\"?([^'\"]*?)'?\"? *" ), "i" ); var m = re.exec( t ); if ( m ) { // Re-organize the match if ( p[i][1] ) m = ["", m[1], m[3], m[2], m[4]]; // Remove what we just matched t = t.replace( re, "" ); break; } } // :not() is a special case that can be optomized by // keeping it out of the expression list if ( m[1] == ":" && m[2] == "not" ) r = jQuery.filter(m[3],r,false).r; // Otherwise, find the expression to execute else { var f = jQuery.expr[m[1]]; if ( f.constructor != String ) f = jQuery.expr[m[1]][m[2]]; // Build a custom macro to enclose it eval("f = function(a,i){" + ( m[1] == "@" ? "z=jQuery.attr(a,m[3]);" : "" ) + "return " + f + "}"); // Execute it against the current filter r = g( r, f ); } } // Return an array of filtered elements (r) // and the modified expression string (t) return { r: r, t: t }; }
filter函数可以分为三段来看待
- 获取正确正则处理函数g(来自于grep,主要是对not选择器进行的调整)
循环开始<直到所有的过滤条件处理完>
- 从给定过滤条件字符串中取出一个过滤条件
- 根据过滤条件获取对应的正则表达式,并通过g函数对目标集合进行过滤
循环结束
获取正确的正则处理函数g,一般的grep是找出符合条件的结果,当在not筛选时要翻转为找出不符合条件的结果。
// Figure out if we're doing regular, or inverse, filtering var g = not !== false ? jQuery.grep : function(a,f) {return jQuery.grep(a,f,true);};
循环开始
while ( t && /^[a-z[({<*:.#]/i.test(t) ) {
...
}
这里的正则表达式,负责检查字符串 t 的开头是不是符合要求的字符。
从给定的过滤条件字符串中取出一个过滤条件
var p = jQuery.parse;for ( var i = 0; i < p.length; i++ ) { var re = new RegExp( "^" + p[i][0] // Look for a string-like sequence .replace( 'S', "([a-z*_-][a-z0-9_-]*)" ) // Look for something (optionally) enclosed with quotes .replace( 'Q', " *'?\"?([^'\"]*?)'?\"? *" ), "i" ); var m = re.exec( t ); if ( m ) { // Re-organize the match if ( p[i][1] ) m = ["", m[1], m[3], m[2], m[4]]; // Remove what we just matched t = t.replace( re, "" ); break; } }
这段代码里引用了jQuery.parse属性,我们看一下parse属性的定义代码:
// The regular expressions that power the parsing engine parse: [ // Match: [@value='test'], [@foo] [ "\\[ *(@)S *([!*$^=]*) *Q\\]", 1 ], // Match: [div], [div p] [ "(\\[)Q\\]", 0 ], // Match: :contains('foo') [ "(:)S\\(Q\\)", 0 ], // Match: :even, :last-chlid [ "([:.#]*)S", 0 ] ]
这两段代码逻辑并不复杂,正则表达式不怎么好的同学一定要沉住气(^_^我看到正则表达式一对一对的特殊字符也眼晕,坚持住就习惯了)。首先,在第一眼看到parse属性的定义时会觉得很莫名奇妙。如果尝试从正则表达式的角度去对里面的内容进行分析的话,你将会被大Q和大S搞的晕头晕脑。然而,如果将parse属性结合最上面filter中的那段代码来分析的话,很容易就找到重点。从最粗略的代码结构看其,filter中这段代码是一个对parse数组的for循环,在循环体对var re赋值的代码我们很快就找到了大Q和大S的奥秘所在。原来,parse中掺杂了作者自己的伪代码思想,即在这一出的for循环中把parse中的伪代码替换并获得最终的正则表达式。接下来的一行代码就是使用正则表达式对目标字符串进行匹配。剩下的代码则是在匹配成功后,对匹配结果和原字符串的处理。对匹配结果进行的处理是按照parse中设置的标记对结果进行重新排序,综合代码可知只有parse中第一行的正则表达式匹配成功的结果需要重新排序。对原字符串的处理则是在匹配成功后一定会执行的处理过程,即将已经提取出的过滤条件从原字符串中移除。最后一句break的作用是,取出一个过滤条件后就跳出分析过滤条件的循环,进入过滤目标集合的阶段。
根据过滤条件获取对应的正则表达式,并通过g函数对目标集合进行过滤
// :not() is a special case that can be optomized by // keeping it out of the expression list if ( m[1] == ":" && m[2] == "not" ) r = jQuery.filter(m[3],r,false).r;// Otherwise, find the expression to execute else { var f = jQuery.expr[m[1]]; if ( f.constructor != String ) f = jQuery.expr[m[1]][m[2]]; // Build a custom macro to enclose it eval("f = function(a,i){" + ( m[1] == "@" ? "z=jQuery.attr(a,m[3]);" : "" ) + "return " + f + "}"); // Execute it against the current filter r = g( r, f ); }
这个阶段的第一行代码,是对not过滤条件的特殊处理(相当于,正常情况下filter筛选符合条件的结果,而当遇到not过滤条件是,则发生翻转,筛选出不符合条件的结果。条件是指:not(条件))。接下来的重点,就是对于jQuery.expr的构造了。这个构造思路在我们理解后显得不是很复杂,但是没有头绪时则会感觉一头雾水。
expr的设计思路是,将jQuery的所有类型的过滤条件,就均分成四组关键字拼接在一起的,如下表:
m1 过滤条件的类型 |
m2
|
m3 | m4 | 示例 |
|---|---|---|---|---|
| '' 标签名称 | 标签名称 | —— | —— | div ; div p |
| '.' 标签的CSS类名 | CSS类名 | —— | —— | .red |
| '#' 标签的id属性 | id属性值 | —— | —— | #bt |
| '@' 标签的指定属性 | 属性名称 | 比较条件(可以指定也可以不指定) | 属性的值(可以指定也可以不指定) | @add='yy' ; @blob |
| ':' 按照判定条件 | 判定条件名称 | 判定的值 | —— | :gt(10) ; :parent |
| ' [ ' 获取长度属性 | 标签名称 | —— | —— | [div |
于是我们有expr的抽象结构如下
expr:{ m1:"m2", m1:{
m2:"m3 m4",
m2:"m3"
}
}
至此filter函数算是完成了。在分析filter的相关代码发现expr的'['过滤器中用到了jQuery.find函数。于是查看jQuery.find的代码定义。
我觉得这里的merge 和 map函数也属于 extend 和 each 类型的函数,对jQuery其它功能的实现提供基本支撑,需要记熟到心里面去。
方向之于生命