QWrap Selector解密之二:从左向右,还是从右向左

QWrap Selector解密之二:从左向右,还是从右向左

关于Selector实现,问得最多的问题是:是从左往右,还是从右往左。

先看一下它们有什么不同,以Selector.query('div span',document.body)为例。看下表:
  从左往右 从右往左
策略简介 先query得到divs,
再通过divs来query得到spans
先query得到spans,再通过是否有父节点是div来过滤
问题 思路简单,但除重与排序麻烦 过滤麻烦,但不用考虑除重与排序
解决方案 如果整个selector里只有后代关系符与亲子关系符,
可以在通过divs来query spans之前,
对divs进行一次过滤(如果前一个包含后一个,则过滤掉后一个),
来避免得到spans后还需要排序。
寻路径过滤时可以使用一些临时缓存策略来提速

事实上,我08年实现的第一版Selector,采用的是从左往右的策略,为很多种情况的query进行了除重与排序的优化,可是始终无法做到理论的完美,特别是关系符很复杂时,例如:“div p~ul li”。在Selector正式使用于项目之前,我终于痛下决心,放弃了从左往右的策略,而采用一种保证理论严谨同时又保证效率的策略。
这策略专门作为注释写进了Selector的代码里:
/*
为了提高查询速度,有以下优先原则:
最优先:原生查询
次优先:在' '、'>'关系符出现前,优先正向(从左到右)查询
次优先:id查询
次优先:只有一个关系符,则直接查询
最原始策略,采用关系判断,即:从最底层向最上层连线,能连线成功,则满足条件
*/
也就是说,它即不是单纯的从左往右,也不是单纯的从右往左
其中,原生查询是在浏览器支持querySelectorAll,并且selector不会有浏览器差异的情况下才采用。
分析一下剩下的几条策略,我们把它编一下号:
    一次优先:在' '、'>'关系符出现前,优先正向(从左到右)查询
    二次优先:id查询
    三次优先:只有一个关系符,则直接查询
    最原始策略,采用关系判断,即:从最底层向最上层连线,能连线成功,则满足条件


通过下表一些选择器例子来理会。
序号 selector 处理顺序
1 refEl.query('div.aaa') 不符一次优先,不符二次优先,符合三次优先,所以,
return refEl.getElements('div.aaa');
2 refEl.query('>div.aaa') 不符一次优先,不符二次优先,符合三次优先,所以,
return refEl.getChildren('div.aaa');
3 refEl.query('+div.aaa') 符合一次优先,所以,
return refEl.getNext('div.aaa');
4 refEl.query('~div.aaa') 符合一次优先,所以,
return refEl.getNexts('div.aaa');
5 refEl.query('div.aaa span.bbb') 不符一次优先,不符二次优先,不符三次优先,用最原始策略,所以,
return refEl.getChildren('span.bbb').filter('div.aaa',refEl);
6 refEl.query('>div.aaa span.bbb') 不符一次优先,不符二次优先,不符三次优先,用最原始策略,所以,
return refEl.getChildren('span.bbb').filter('>div.aaa',refEl);
7 refEl.query('+div.aaa span.bbb') 符合一次优先,所以,先用第一次优先原则切除左边的第一个关系符与自选器,
return refEl.getNext('div.aaa').query(' span.bbb');
即:
return refEl.getNext('div.aaa').getElements('span.bbb');
注:对于本例子的处理,jquery的策略会bug,
参见:http://www.cnblogs.com/rubylouvre/archive/2011/01/24/1942818.html#2018130
8 refEl.query('~div.aaa span.bbb') 符合一次优先,所以,先用第一次优先原则切除左边的第一个关系符与自选器,
return refEl.getNexts('div.aaa').query(' span.bbb');
即:
return refEl.getNexts('div.aaa').getElements('span.bbb');
注:对于本例子的处理,jquery的策略会bug,
参见:http://www.cnblogs.com/rubylouvre/archive/2011/01/24/1942818.html#2018130
9 refEl.query('div div.aaa span.bbb') 不符一次优先,不符二次优先,不符三次优先,用最原始策略,所以,
refEl.getChildren('span.bbb').filter('div div.aaa',refEl)。
10 refEl.query('+div.aaa+ul span.bbb') 符合一次优先,所以,先用第一次优先原则切除左边的第一个关系符与自选器,
return refEl.getNext('div.aaa').query('+ul span.bbb');
切后的右半部分,变成了例7。
11 refEl.query('~div.aaa+ul span.bbb') 符合一次优先,所以,先用第一次优先原则切除左边的第一个关系符与自选器,
return refEl.getNexts('div.aaa').query('+ul span.bbb');
切后的右半部分,变成了例7。 注:本例存在结果中有重复元素的可能。。。有人能说出为什么吗?
12 refEl.query('~div.aaa~ul span.bbb') 符合一次优先,所以,先用第一次优先原则切除左边的第一个关系符与自选器,
return refEl.getNexts('div.aaa').query('~ul span.bbb');
不过,这样做明显有重复,需要需要在第二次query之前,只保留一个:
return refEl.getNexts('div.aaa')[0].query('~ul span.bbb');
切后的右半部分,变成了例8。
13 refEl.query('div#id') 不符一次优先,符合二次优先,所以,
return refEl.getElementById('id').filter('div#id',refEl);
14 refEl.query('div div#id') 不符一次优先,符合二次优先,所以,
return refEl.getElementById('id').filter('div div#id',refEl);
15 refEl.query('div div#id span.bbb') 不符一次优先,符合二次优先,所以,
return refEl.getElementById('id').filter('div div#id',refEl).query('span.bbb');
注:这一个是从中间查的典型例子,通过#id所在的自选器,将selector分成两步分。
16 refEl.query('~div div#id') 符合一次优先,所以,
return refEl.getNexts('div').query('div#id');
注:虽说refEl.getNexts('div')是多个,但refEl.getNexts('div').query('div#id');还是最多只有一个。
17 .... ....



从上面的诸多例子可以看到,除了极少数的情况会有重复结果的可能之外,绝大多数选择器并不需要排序与除重。

另外,在filter方法中,查找路径从是从右往左找路径的。上面的诸多例子里使用的filter时的参数selector,都不是以“+”或“~”开头的。
QWrap偷了下懒,filter的selector参数,不支持以“+”或“~”,这个算是一个理论的缺憾。。。不过ms只是理论问题,实际中很少碰到,碰到的话请换方法解决。

另:与本文所提到的方向有关的代码,主要集中在私有方法querySimple里

/*
* querySimple(pEl,sSelector): 得到以pEl为参考,符合过滤条件的HTML Elements.
* @param {Element} pEl 参考元素
* @param {string} sSelector 里没有","运算符
* @see: query。
*/

function querySimple(pEl, sSelector) {
querySimpleStamp
++;
/*
为了提高查询速度,有以下优先原则:
最优先:原生查询
次优先:在' '、'>'关系符出现前,优先正向(从左到右)查询
次优先:id查询
次优先:只有一个关系符,则直接查询
最原始策略,采用关系判断,即:从最底层向最上层连线,能连线成功,则满足条件
*/
...
}

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

posted on 2011-05-21 19:59  JKisJK  阅读(1797)  评论(0编辑  收藏  举报

导航