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