在jquery.fn.init()中,我们对这句jQuery(context).find(selector)没有深入去分析,在使用$()时候,大部分时间都是在使用这句来完成功能的。它就是调用CSS Selector到Dom树去查找和相符CSS语法的元素节点(集)。jQuery名字中query的意义就体现在这里。
根据符合CSS语法的字符串,它是怎么到DOM文档树去找到符合条件的元素呢?无论怎么解析这个字符串,它总得有调用最原始的函数来完成功能,这些函数是什么?
在没有分析Selector源码之前,说来也不会相信功能强大的selector是建立在元素的getElementsByTagName,getElementById直接引用和元素的childNodes firstChild、lastChild、nextSibling、parentNode、previousSibling等间接引用这些函数的使用。
在分析源码之前,我们得了解一下CSS selector,它可以粗略分成几类基本的类型:ID选择器(#id),Class选择器(. class ),类型(type)选择器(p),Combinators,属性(Attribute)选择器,Pseudo Classes选择器等。这些都是单一的选择器,可以在应用中把它们组合起来,如:div#id, div:last-child。
我们先分析一个例子:span[ class =’test’],它是属性选择器,我们一般把它做一个整体来理解:在Dom文档树中找到其class 等于’test’的且标签为span的元素。这是一步到位,直接从dom文档树查找(select)所需要的元素。
其实我们更细一点分析,完成可以把这个字符串拆分成两步走,第一步是根据document.getElementsByTagName(tagName)取得span的元素集,第二步是再根据其 class 是否等于test来进行判断,把不等于的元素从结果集中去掉。
不光是属性选择器,对所有的复合的选择器,都可以根据这种细粒度来拆分选择器,接下来就是分别是每个小部分进行操作。如果这样,我们就能把CSS selector分成两大类,第一类是是选择(selector),从根据给定的selector从Dom树找到相关的元素节点放到结果集中来。第二类是筛选(filter)。在结果集中判断该元素还满足表达式。
这样一来,就完全可以JS的原有直接或间接对Dom元素进行引用的方法,如div#id就可以变成先找到div的元素,之后就判断其元素的id是否等于id,如果不等于就从结果集中删除掉。而对于div[id=’bar’]也是一样,可以看出div#id与div[id=’id’]是一样的操作。对于div.class 也可以转换到属性中去操作。
现在要做的事就分析那些基本选择器是完成选择(selector)还是筛选(filter)的工作。我们把类型选择器,Combinators统一称为元素选择器,其形式如下:*,E F,E~F,E+F,E>F,E/F,E。这些一定是从dom树中选择元素。
对于ID选择器和Class选择器,它们也可以在selector字符串的起始位置,说明他们也可以完成选择(selector)功能。当它们不在起始位置上,如div#id,div.class 那他们就是筛选器。我们可以变通一下统一起来,对于ID选择器可以采用document.getElementById来直接引用。但是对于.class 就不一样了,它没有相对的函数。这种情况下,我们可以把它变成*.class 。*能取得一个范围内的所有的元素,然后再进行筛选。这样对于ID选择器,它有专门的函数来处理,是选择(selector)器。对Class选择器,它是筛选器,特殊情况就是对于*标签取到元素集进行筛选。
其它如属性(Attribute)选择器,Pseudo Classes都是筛选器,只能附在一个元素或多个元素后面。,特殊情况就是对于*标签取到元素集进行筛选。
看一个:div.foo:nth-child(odd)[@foo =bar]#aId > a的例子:
第一步:找到div type选择器的所有元素。
第二步:在这些元素中找到class 为foo的元素。如果没有就根本不要去分析下面的字符。
第三步:根据:nth-child(odd)找到元素的子孩子元素为偶数的的元素。进一步筛选了。
第四步:找到[],就是属性筛选器了,根据foo属性值bar来判断集合中还有那些满足条件。
第五步:在这些元素集合,找到id=aId的元素,进一步筛选。
第六步:在这些元素集合中找到所有元素对应的子元素类型为a的所有子元素。
结果:我们可以看到,现在元素是子元素的集合。
讲完了通版的大道理,接下来就是来看看jQuery是怎么实现的:
find : function(selector) {
var elems = jQuery.map(this , function(elem) {
return jQuery.find(selector, elem); });
return this .pushStack(/[^+>] [^+>]/.test(selector) ? jQuery
.unique(elems) : elems);
},
这是jquery.fn.init调用的find方法。它只是完成之本本对象集合中每一个元素都调用jQuery.find(selector, elem)方法,组合成新unique集合后构建新jquery对象。最重要的实质性的工作在jQuery.find(selector, elem)完成:
find : function(t, context) {
if (typeof t != "string" )return [t];
if (context && context.nodeType != 1 && context.nodeType != 9 )
return [];
context = context || document;
var ret = [context], done = [], last, nodeName;
while (t && last != t) {
var r = [];
last = t;
t = jQuery.trim(t);
var foundToken = false , re = quickChild,
m = re.exec(t);
if (m) { ①
nodeName = m[ 1 ].toUpperCase();
for (var i = 0 ;ret[i]; i++)
for (var c = ret[i].firstChild;c; c = c.nextSibling)
if (c.nodeType == 1 && (nodeName == "*" ||
c.nodeName.toUpperCase() == nodeName))
r.push(c);
ret = r;
t = t.replace(re, "" );
if (t.indexOf(" " ) == 0 )continue ;
foundToken = true ;
}
else {
re = /^([>+~])\s*(\w*)/i;
if ((m = re.exec(t)) != null ) {
r = [];
var merge = {};
nodeName = m[ 2 ].toUpperCase();
m = m[ 1 ];
for (var j = 0 , rl = ret.length;j < rl; j++) {
var n = (m == "~" || m == "+"
? ret[j].nextSibling: ret[j].firstChild);
for (;n; n = n.nextSibling)
if (n.nodeType == 1 ) {
var id = jQuery.data(n);
if (m == "~" && merge[id])
break ;
if (!nodeName|| n.nodeName.toUpperCase() == nodeName) {
if (m == "~" ) merge[id] = true ;
r.push(n);
}
if (m == "+" ) break ;
}
}
ret = r;
t = jQuery.trim(t.replace(re, "" ));
foundToken = true ;
}
}
if (t && !foundToken) { ③
if (!t.indexOf("," )) { ④
if (context == ret[0 ]) ret.shift();
done = jQuery.merge(done, ret);
r = ret = [context];
t = " " + t.substr(1 , t.length);
}
else { ⑤
var re2 = quickID;
var m = re2.exec(t);
if (m) {m = [0 , m[2 ], m[3 ], m[1 ]];
else { re2 = quickClass;
m = re2.exec(t);
m[2 ] = m[2 ].replace(/\\/g, "" );
var elem = ret[ret.length - 1 ];
if (m[1 ] == "#" && elem && elem.getElementById
&& !jQuery.isXMLDoc(elem)) {
var oid = elem.getElementById(m[2 ]);
if ((jQuery.browser.msie || jQuery.browser.opera) && oid
&& typeof oid.id == "string" && oid.id != m[2 ])
oid = jQuery( '[@id="' + m[2 ] + '"]' , elem)[0 ];
ret = r = oid && (!m[ 3 ] || jQuery.nodeName(oid, m[3 ]))
? [oid]: [];
}
else {
for (var i = 0 ;ret[i]; i++) {
var tag = (m[ 1 ] == "#" && m[3 ] ? m[3 ] : (m[1 ] != ""
|| m[ 0 ] == "" ? "*" : m[2 ]));
if (tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
tag = "param" ;
r = jQuery.merge(r, ret[i].getElementsByTagName(tag));
}
if (m[1 ] == "." ) r = jQuery.classFilter(r, m[2 ]); ⑦
if (m[1 ] == "#" ) {
var tmp = [];
for (var i = 0 ;r[i]; i++)
if (r[i].getAttribute("id" ) == m[2 ]) {
tmp = [r[i]];
break ;
}
r = tmp;
}
ret = r;
}
t = t.replace(re2, "" );
}
}
if (t) {
var val = jQuery.filter(t, r); ⑧
ret = r = val.r;
t = jQuery.trim(val.t);
}
}
if (t) ret = [];
if (ret && context == ret[0 ])
ret.shift();
done = jQuery.merge(done, ret);
return done;
},
上面find的实现的代码有点长。其实分析起来也不是很难。①②③④⑤处两个if -else 是元素选择器,针对>/+~ F或#id,.class tagName,div#id这样的选择器进行查找元素,构成结果集。⑥实现的就是分析它之后的属性选择器进行筛选。这段代码说白就是select与Filte之间的交互分析CSS selector的字符串进行查找或筛选的过程。
具体的细节在代码中有分析。在⑦处它采用先找到所有元素然后对每个元素进行 class 的判断来分析如.class 这样selector。因为它在起始位说明是查找器。但是这里对这个最小的单元部分,我们还可能两个部分,找到所有元素,然后一个个排查,排查就是采用了jQuery.classFilter(r, m[2 ]):
classFilter : function(r, m, not) {
m = " " + m + " " ;
var tmp = [];
for (var i = 0 ;r[i]; i++) {
var pass = ( " " + r[i].className + " " ).indexOf(m) >= 0 ;
if (!not && pass || not && !pass)
tmp.push(r[i]);
}
return tmp;
},
jQuery.classFilter是比较简单。在find()的第二个部分是筛选,它调用jQuery.filter(t, r)来完成功能:
filter : function(t, r, not) {
var last;
while (t && t != last) {
last = t;
var p = jQuery.parse, m;
for (var i = 0 ;p[i]; i++) {
m = p[i].exec(t);
if (m) {
t = t.substring(m[ 0 ].length);
m[ 2 ] = m[2 ].replace(/\\/g, "" );
break ;
}
}
if (!m) break ;
if (m[1 ] == ":" && m[2 ] == "not" )
r = isSimple.test(m[ 3 ])
? jQuery.filter(m[ 3 ], r, true ).r: jQuery(r).not(m[3 ]);
else if (m[1 ] == "." )
r = jQuery.classFilter(r, m[ 2 ], not);
else if (m[1 ] == "[" ) {
var tmp = [], type = m[ 3 ];
for (var i = 0 , rl = r.length;i < rl; i++) {
var a = r[i], z = a[jQuery.props[m[ 2 ]] || m[2 ]];
if (z == null || /style|href|src|selected/.test(m[2 ]))
z = jQuery.attr(a, m[ 2 ]) || '' ;
if ( (type == "" && !!z
|| type == "=" && z == m[5 ]
|| type == "!=" && z != m[5 ]
|| type == "^=" && z&& !z.indexOf(m[5 ])
|| type == "$=" && z.substr(z.length - m[5 ].length) == m[5 ] || (type == "*=" || type == "~=" )&& z.indexOf(m[5 ]) >= 0
)
^ not)
tmp.push(a);
}
r = tmp;
}
else if (m[1 ] == ":" && m[2 ] == "nth-child" ) {
var merge = {}, tmp = [],
test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[ 3 ] == "even" && "2n"
|| m[ 3 ] == "odd" && "2n+1" || !/\D/.test(m[3 ]) && "0n+"
+ m[ 3 ] || m[3 ]),
first = (test[ 1 ] + (test[2 ] || 1 )) - 0 , last = test[3 ] - 0 ;
for (var i = 0 , rl = r.length;i < rl; i++) {
var node = r[i], parentNode = node.parentNode,
id = jQuery.data(parentNode);
if (!merge[id]) {
var c = 1 ;
for (var n = parentNode.firstChild;n; n = n.nextSibling)
if (n.nodeType == 1 )n.nodeIndex = c++;
merge[id] = true ;
}
var add = false ;
if (first == 0 ) {
if (node.nodeIndex == last)
add = true ;
}
else if ((node.nodeIndex - last) % first == 0
&& (node.nodeIndex - last) / first >= 0 )
add = true ;
if (add ^ not) tmp.push(node);
}
r = tmp;
}
else {
var fn = jQuery.expr[m[ 1 ]];
if (typeof fn == "object" )
fn = fn[m[ 2 ]];
if (typeof fn == "string" )
fn = eval( "false||function(a,i){return " + fn + ";}" );
r = jQuery.grep(r, function(elem, i) {
return fn(elem, i, m, r);
}, not);
}
}
return {
r : r,
t : t
};
},
jQuery.filter完成分析属性([])、Pseudo(:), class (.),id(#),的筛选的功能。从给定的集合中筛选出满足上面四种筛选表达式的集合。针对于find()。这个filter完成不表明整个selector的分析完成了。还会交替地通过查找器来查找或通过该函数来筛选。对于单独使用这个函数,表达式中就不应该含有查找的selector表达式了。筛选是根据[、:、#、.这四个符号来做为筛选器的分隔符的。
class 筛选器是通过classFilter来完成。它还把Pseudo中:not、:nth-child单独从Pseudo类的中单独提起出来处理。对于[的属性筛选器,实现起来也是很简单。除去这些,它还调用jQuery.expr[m[1 ]];来处理Pseudo类,而jQuery还做了扩展。jQuery.expr中的Pseudo类有以下几个部分:
如果想详细了解这样怎么用吧,推荐由一揪整理编辑jquery的中文文档。,
对于CSS selector,尽管多次分析domQuery,和jquery Selector。尽管自己也吃透。但是感觉到写出来还是不到位。如果读者之前没有接受这方面。建议仔细分析上面的代码和注释。找一个复杂的例子,自行分析一下,或许可能弄懂selector的设计。
posted @
2008-12-03 10:13
瑞尼书苑
阅读(
973 )
评论()
编辑
收藏
举报