代码改变世界

(八)jQuery.extend代码段

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

分析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根据后续条件进行过滤。
    1. 获取token,如果获取到token则获取token指定的集合
    2. 在没有获取到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 Sad的代码,这两句代码起到什么效果呢?

紧跟后面是几个内部变量的定义。

循环开始的条件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在获取的集合上做过滤。