(八)jQuery.extend代码段
分析jQuery.find的实现代码,发现其调用了 getAll 、trim、sliding三个方法。
getAll: function(o,r) { r = r || []; var s = o.childNodes; for ( var i = 0; i < s.length; i++ ) if ( s[i].nodeType == 1 ) { r.push( s[i] ); jQuery.getAll( s[i], r ); } return r; } trim: function(t){ return t.replace(/^\s+|\s+$/g, ""); } sibling: function(elem, pos, not) { var elems = []; var siblings = elem.parentNode.childNodes; for ( var i = 0; i < siblings.length; i++ ) { if ( not === true && siblings[i] == elem ) continue; if ( siblings[i].nodeType == 1 ) elems.push( siblings[i] ); if ( siblings[i] == elem ) elems.n = elems.length - 1; } return jQuery.extend( elems, { last: elems.n == elems.length - 1, cur: pos == "even" && elems.n % 2 == 0 || pos == "odd" && elems.n % 2 || elems[pos] == elem, prev: elems[elems.n - 1], next: elems[elems.n + 1] }); }
getAll的定义为,将o对象的所有子级节点获取到r集合中。代码内使用了递归调用。
trim则为通过正则表达式将字符串首尾的连续空白字符移除。
sibling为获取兄弟节点的意思。not参数表示是否将自己也包含仅结果结合中,为true时则表示不包含,否则包含进结果集合里。注意该函数实现代码里在return时,拓展了结果集合的几个属性。last属性-表示自己是否是兄弟节点中的最后一个。cur属性-表示自己是否出现在指定的位置,位置属性有三种:奇数位置、偶数位置、指定位置。pref属性-表示自己的前一个兄弟节点。next属性-表示自己的后一个兄弟节点。
接下来我们看find函数的实现代码:
find: function( t, context ) { // Make sure that the context is a DOM Element if ( context && context.nodeType == undefined ) context = null; // Set the correct context (if none is provided) context = context || jQuery.context || document; if ( t.constructor != String ) return [t]; if ( !t.indexOf("//") ) { context = context.documentElement; t = t.substr(2,t.length); } else if ( !t.indexOf("/") ) { context = context.documentElement; t = t.substr(1,t.length); // FIX Assume the root element is right :( if ( t.indexOf("/") >= 1 ) t = t.substr(t.indexOf("/"),t.length); } var ret = [context]; var done = []; var last = null; while ( t.length > 0 && last != t ) { var r = []; last = t; t = jQuery.trim(t).replace( /^\/\//i, "" ); var foundToken = false; for ( var i = 0; i < jQuery.token.length; i += 2 ) { var re = new RegExp("^(" + jQuery.token[i] + ")"); var m = re.exec(t); if ( m ) { r = ret = jQuery.map( ret, jQuery.token[i+1] ); t = jQuery.trim( t.replace( re, "" ) ); foundToken = true; } } if ( !foundToken ) { if ( !t.indexOf(",") || !t.indexOf("|") ) { if ( ret[0] == context ) ret.shift(); done = jQuery.merge( done, ret ); r = ret = [context]; t = " " + t.substr(1,t.length); } else { var re2 = /^([#.]?)([a-z0-9\\*_-]*)/i; var m = re2.exec(t); if ( m[1] == "#" ) { // Ummm, should make this work in all XML docs var oid = document.getElementById(m[2]); r = ret = oid ? [oid] : []; t = t.replace( re2, "" ); } else { if ( !m[2] || m[1] == "." ) m[2] = "*"; for ( var i = 0; i < ret.length; i++ ) r = jQuery.merge( r, m[2] == "*" ? jQuery.getAll(ret[i]) : ret[i].getElementsByTagName(m[2]) ); } } } if ( t ) { var val = jQuery.filter(t,r); ret = r = val.r; t = jQuery.trim(val.t); } } if ( ret && ret[0] == context ) ret.shift(); done = jQuery.merge( done, ret ); return done; }
find函数的代码也比较多,粗略浏览下来注意到查找Token的循环与filter中查找parse的循环类似。find函数中引用到了jQuery.token属性,查看jQuery.token代码如下:
token: [
"\\.\\.|/\\.\\.", "a.parentNode",
">|/", "jQuery.sibling(a.firstChild)",
"\\+", "jQuery.sibling(a).next",
"~", function(a){
var r = [];
var s = jQuery.sibling(a);
if ( s.n > 0 )
for ( var i = s.n; i < s.length; i++ )
r.push( s[i] );
return r;
}
]
审视token中的值定义,发现token中定义的内容类似XPath的作用。多次阅读find代码之后发更加肯定这一点。按照filter中使用的方法给find划分逻辑结构如下:
- 处理find作用的对象和作用条件。
循环开始<直到没有了查找条件>
- 获取查询集合,传递给filter根据后续条件进行过滤。
- 获取token,如果获取到token则获取token指定的集合
- 在没有获取到token时,检查是否是条件分隔符','和'|',如果是则将之前处理的结果保存到最终结果里,并初始画当前集合。检查是否是作用于全局的查找条件(按照id查找,按照标签名查找,所有子节点),获取集合。
- 在获取的集合上应用过滤条件
循环结束
处理find作用对象和作用条件:
// Make sure that the context is a DOM Element if ( context && context.nodeType == undefined ) context = null;// Set the correct context (if none is provided) context = context || jQuery.context || document;if ( t.constructor != String ) return [t];if ( !t.indexOf("//") ) { context = context.documentElement; t = t.substr(2,t.length); } else if ( !t.indexOf("/") ) { context = context.documentElement; t = t.substr(1,t.length); // FIX Assume the root element is right :( if ( t.indexOf("/") >= 1 ) t = t.substr(t.indexOf("/"),t.length); }var ret = [context]; //定义了存储中间结果的变量 var done = []; // 存储最终结果的变量 var last = null; //存储上一次处理的条件
如上代码中,前三行代码操作了context参数。主要操作为首先判断传入的context是不是正确的html节点。如果不是的话将context设置为null。接下来对context赋值,这里需要注意jQuery.contex在哪里定义的呢?
剩下的代码是对find的作用条件t进行处理。当传入的t不是字符串时则直接返回[t]。当t是字符串时,如果t开头为'//' 或'/' 则将context设置为当前文档的根节点。需要注意代码中注释了 //FIX Assume the root element is right
的代码,这两句代码起到什么效果呢?
紧跟后面是几个内部变量的定义。
循环开始的条件while ( t.length > 0 && last != t ) { ... } 。这表示当作用条件为空或者作用条件不能再被识别时结束(注意last的意义)。
获取查询集合,传递给filter根据后续条件进行过滤。
获取token。用到正则表达式匹配token。
var r = []; last = t; t = jQuery.trim(t).replace( /^\/\//i, "" );var foundToken = false;for ( var i = 0; i < jQuery.token.length; i += 2 ) { var re = new RegExp("^(" + jQuery.token[i] + ")"); var m = re.exec(t); if ( m ) { r = ret = jQuery.map( ret, jQuery.token[i+1] ); t = jQuery.trim( t.replace( re, "" ) ); foundToken = true; } }
如果没有获取到token
if ( !foundToken ) { if ( !t.indexOf(",") || !t.indexOf("|") ) { if ( ret[0] == context ) ret.shift(); done = jQuery.merge( done, ret ); r = ret = [context]; t = " " + t.substr(1,t.length); } else { var re2 = /^([#.]?)([a-z0-9\\*_-]*)/i; var m = re2.exec(t); if ( m[1] == "#" ) { // Ummm, should make this work in all XML docs var oid = document.getElementById(m[2]); r = ret = oid ? [oid] : []; t = t.replace( re2, "" ); } else { if ( !m[2] || m[1] == "." ) m[2] = "*"; for ( var i = 0; i < ret.length; i++ ) r = jQuery.merge( r, m[2] == "*" ? jQuery.getAll(ret[i]) : ret[i].getElementsByTagName(m[2]) ); } } }
在获取的集合上应用过滤条件
if ( t ) { var val = jQuery.filter(t,r); ret = r = val.r; t = jQuery.trim(val.t); }
在这里需要思考find与filter的关系。我认为,find与filter存在着这样一种关系:find在filter之前被调用。也就是说,find判断filter所要作用的集合,filter在获取的集合上做过滤。
方向之于生命