代码改变世界

(七)jQuery.extend代码段

2012-02-12 15:12  kwjlk  阅读(298)  评论(0编辑  收藏  举报

就过之前对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

 

m3m4示例
'' 标签名称 标签名称 —— —— 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其它功能的实现提供基本支撑,需要记熟到心里面去。