QWrap Selector解密之三:matchesSelector

QWrap Selector解密之三:matchesSelector

w3c的selector-api2标准已经提供了matchSelector的相关条文:
http://dev.w3.org/2006/webapi/selectors-api2/#matchtesting

module dom {
[Supplemental]
interface Element {
boolean matchesSelector(in DOMString selectors, in optional Element refElement);
boolean matchesSelector(in DOMString selectors, in sequence<Node> refNodes);
};
};


Firefox也有个对应的方法:https://developer.mozilla.org/en/DOM/Node.mozMatchesSelector,不过,它相对于标准的matchesSelector,明显少了一个refElement的参数。
QWrap也有一个与标准几乎一致的方法Selector.filter。

/**
* 用一个css selector来过滤一个数组.
* @method filter
* @static
* @param {Array|Collection} els: 元素数组
* @param {string} sSelector: 过滤selector,这个selector里的第一个关系符不可以是“+”“~”。
* @param {Element} pEl: 父节点。默认是document
* @returns {Array} : 返回满足过滤条件的元素组成的数组。
*/
Selector.filter
= function(els, sSelector, pEl) {
//...
}


与标准相比,不同点有:
1。标准方法叫matchSelector,这个方法叫filter,用法不同。
2。sSelector是相对于参考元素的,而不是document,所以,也不必先写:scope伪类。
3。sSelector的起始关系符需要是亲子关系或后代关系,而不允许是兄弟关系。即,不可以是“+”“~”

这个方法的思路大略是:取出els[i],然后用sSelector来作路线指导,寻找关系路线,一直找到pEl,找得到,则通过过滤,反之则不通过。
例如:html结构是这样的:html>body>div#div1.div1>div#div2.div2>span#span1.span1,通过下表我们看下各种情况下的执行过程:

序号 selector 处理顺序
1 filter([span1],'span.span3',body) 先判断span1本身是否满足"span.span3",NG
结论:未通过过滤
2 filter([span1],'span.span1',body) 先判断span1本身是否满足"span.span1",OK
再判断body与span1是否是后代关系,OK
结论:通过过滤
3 filter([span1],'>span.span1',div2) 先判断span1本身是否满足"span.span1",OK
再判断div2与span1是否是亲子关系,OK
结论:通过过滤
4 filter([span1],'>span.span1',body) 先判断span1本身是否满足"span.span1",OK
再判断body与span1是否是亲子关系,NG
结论:未通过过滤

再看更多层次时,对应的执行过程:

序号 selector 处理顺序
11 filter([span1],'div span',body) 先判断span1本身是否满足"span",OK
通过span1寻找"div ",即找div祖先,得到div2,OK
再判断body与div2是否是后代关系,OK
结论:通过过滤
12 filter([span1],'table span',body) 先判断span1本身是否满足"span",OK
通过span1寻找"table ",即找table祖先,NG
结论:未通过过滤
13 filter([span1],'html span',body) 先判断span1本身是否满足"span",OK
通过span1寻找"html ",即找html祖先,上寻时通过body时还没找到,NG
结论:未通过过滤
注:虽说上寻到body上层可以找到html,但是这也不符合参考body的“ html span”
14 filter([span1],'>div span',body) 先判断span1本身是否满足"span",OK
通过span1寻找"div ",即找div祖先,得到div2,OK
再判断body与div2是否是亲子关系,NG
回溯,发现可以继续从div2再向上找div,找到div1,OK
再判断body与div1是否是亲子关系,OK
结论:通过过滤
注:这里有一个回溯的过程,即向上寻路时,如果分支不通,需要回退再寻他路。
注:sizzle没有处理这种回溯的情况,所以某些情况下会出现错误,参见:
http://www.cnblogs.com/rubylouvre/archive/2011/01/24/1942818.html#2018177
15 filter([span1],'>div>span',body) 先判断span1本身是否满足"span",OK
通过span1寻找"div>",即找div父亲,得到div2,OK
再判断body与div2是否是亲子关系,NG
结论:未通过过滤
想一想:为什么这里不需要回溯???
16 filter([span1],'table div span',body) 先判断span1本身是否满足"span",OK
通过span1寻找"div ",即找div祖先,得到div2,OK
通过div2寻找"table ",即找table祖先,NG
结论:未通过过滤
想一想:为什么这里不需要回溯???
17 ... ...



注,为方便理解,以上例子中都没有提到“+”“~”关系符,有兴趣的同学可以举一反三一下。
由于回溯是一件很耗cpu的事,所以,我们需要尽量不做无谓的回溯。而是否需要回溯,或在哪一步需要回溯,这个又需要通过整个selector的所有关系符来判断。
sizzle使用候选集加映射集的方式来实现query,这导致他在作是否回溯的决定时,没法全盘考虑这个selector中的所有关系符以及当前关系符的位置,他很难作这个决定。现在的情形看来,他索性偷了个懒,任何情况下都不作回溯了。----事实上,需要回溯的情况在现实中很少见,所以,Sizzle没有考虑,也很少有人发现这个问题。
QW.Selector里关于,把“ ”“~”当作需回溯关系符,而把一个selector中的所有关系符串成relationsStr,再通过“(/[+>~] |[+]~/.test(relationsStr))”来判断是否需要回溯。
例如:
“ table div span”的关系符串起来是三个空格,(/[+>~] |[+]~/.test(relationsStr))的结果是false,即,不需要回溯;
“>div>span”的关系符串起来是“>>”,不需要回溯;
“>div span”的关系符串起来是“> ”,需要回溯;
......


明白了matchesSelector的思路,即“按selector寻路径,将el与pEl连起来”,之后,再看实现代码就会很清晰了。
事实上,由于这个方法的sSelector参数里可能有“,”并联关系符,所以,真正实现filter的函数是一个内部函数:    (注意一下,参数与filter的参数顺序不一致,而sltors是已经将sSelector拆成了关系符与自选器的配对数组)

/*
判断一个长辈与子孙节点是否满足关系要求。----特别说明:这里的第一个关系只能是父子关系,或祖孙关系;
*/
function filterByRelation(pEl, els, sltors) {
//...
}


附:QWrap网址:http://www.qwrap.com

posted on 2011-05-23 14:29  JKisJK  阅读(2188)  评论(1编辑  收藏  举报

导航