jQuery 选择器之虚拟选择器 (pseudo selector)

介绍一些虚拟选择器,并且说明如何扩充虚拟选择器。 :)
基本上 selector 严格分类来讲有相当多种, slibing / parent ...etc

可以看这里有分类
http://api.jquery.com/category/selectors/

CSS 提供给我们的那些相信大家都不陌生,ID(#)/Tag/class(.)/attr([])/child(>) ...etc

其中我要介绍的是一些算是比较不属于 css3 规範的异类 selector ,
大家可能常用(至少我常用),但是不见得知道他的运作模式,

这里我们就是要来告诉你他是什么。:)

jQuery 的 selector 其实暗藏很多我们不知道的玄机,我会尽量再多介绍一些。


ok 不卖关子,我们开始谈主题。

大家有没有用过 pseudo selector ,像是这个範例?

<div class="test"></div><div class="test" style="display:none"></div><div class="test" style="display:none"></div><script>   alert($(".test:visible").length);</script>

Runnable sample : http://jsfiddle.net/GGRD6/

":visible" selector,我相信大家都知道这不是 css 规格,但是它会动。:)

这种冒号开头的选择器,jQuery 称之为虚拟选择器。
(其实还再细分出一种是 pos selector
如 :eq / :lt / :odd ..etc ,剩下的都称为 pseudo selector,
但这里我先统称为 pseudo selector 以便说明,原理是相近的。)

这时候我们就会需要来看看 jQuery 官方文件:
http://api.jquery.com/visible-selector/

Because :visible is a jQuery extension and not part of the CSS specification, queries using :visible cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :visible to select elements, first select the elements using a pure CSS selector, then use .filter(":visible").

(不负责翻译)

因为 :visible 是一个 jQuery extension 而非 CSS 规格,使用 :visible 查询时,
将无法利用到原生的 querySelectorAll() 函式,会比较慢。
要获得最好效能,建议是先使用纯 css 查询再使用 filter 进行过滤。

重要的是这里印证虚拟选择器大多都不是 css 官方规格,
这里他同时说两件我们需要知道的事情,但效能部份我们先不管,
我们文末会回头再谈,我们先介绍虚拟选择器的原理。


为了说明,我想 :visible 一个例子是不够的,我再举另一个例子 ":contains()" 好了,
这也是相当常见的例子,寻找指定元素内有包含特定字串的。

<div>    <div class="test" style="display:none">I am a div </div>    <div class="test" style="display:none"> I am also div  </div>    <div class="test" style="display:none">I am still a div  </div></div>​<script>$(document.body).append(".test:contains('div') #=> "+ $(".test:contains('div')").length +"<br />");$(document.body).append(".test:contains('still') #=> "+ $(".test:contains('still')").length  +"<br />");​</script>

将会输出
.test:contains('div') #=> 3
.test:contains('still') #=> 1

runnable sample http://jsfiddle.net/GGRD6/1/

官方文件 http://api.jquery.com/contains-selector/


当然还有 :not 这个一定是不可少的。

<div>    <div class="test test1" style="display:none">I am a div </div>    <div class="test test2" style="display:none"> I am also div  </div>    <div class="test test3" style="display:none">I am still a div  </div></div><script>$(document.body).append(".test:not('.test1') #=> "+ $(".test:not('.test1')").length +"<br />");$(document.body).append(".test:not('.test1,.test2') #=> "+ $(".test:not('.test1,.test2')").length  +"<br />");​</script>​

输出
.test:not('.test1') #=> 2
.test:not('.test1,.test2') #=> 1

官方文件 http://api.jquery.com/not-selector/


你一定很好奇为什么我要特别介绍这个,我可以一个一个作 selector 的介绍,
为什么要介绍「pseudo selector 」,他有什么好介绍的?

除了效能议题以外,其实我更重视的是他实作的逻辑,
我曾经很好奇 jQuery 是怎么实作的而翻过很多次他的原始码,
而其中这一个是我特别有印象的。:)

他有趣的地方在于一是实作跟虚拟选择器的对应,另外就是他的可扩充性。

以我们 JS developer 来讲,
碰到 selelector 这种带着规则的「字串」东西其实蛮没辙的,
他又不是 call 一个纯 js function ,你如果想 trace 也要多绕好几圈才看的懂。

而 pseudo selector 的条件定义呢,
就由 jQuery.expr.filters 跟 jQuery.expr.setFilters (for pos selectors) 掌管,
所以你可以翻原始码,也可以用这种方式找出所有虚拟选择器:

var filters = jQuery.expr.filters;for(var i in filters  ){    $(document.body).append(i+" #=> "+filters [i].toString()+"<br /><br />");   }$(document.body).append("<div style='color:red;'>setFilters </div><br />");   filters = jQuery.expr.setFilters;for(var i in filters  ){    $(document.body).append(i+" #=> "+filters [i].toString()+"<br /><br />");   }

因为 details 有点多,想看细节的实作的可以移步 XD
http://jsfiddle.net/yAAqD/

翻翻 jQuery 1.7.2 的 4565 ~ 4686 行程式码~(这只是其中部份。)

你会发现他基本上流程是这么作:

1.先检查是不是 pos selector ,是的话就跑 jQuery.expr.setFilters 出来作 filter。
(:nth , :eq, :gt, :lt, :first, :last, :even, :odd )

2.如果不是,就找 jQuery.expr.filters

如果后面还有跟其他 selector ,会再套叠后续的 operator 去做查询。


你可能会好奇,如果我给 $(".test:myfilter") 会发生什么事情,答案是会喷 error:
Syntax error, unrecognized expression: myfilter


所以理论上要扩充一个新的 filter ,直接扩充 jQuery.expr.filters 就可以了。
(如果你要扩充 set selector ,要改比较多东西,有兴趣再问吧XD)

举个例子,我今天比较机车,我想留 id 里面有包含 tony 的元素,我可以这样实作。

jQuery.expr.filters.isTony = function(elem){      return elem.id && elem.id.indexOf("tony") != -1;}//query with the pseudo selector //$("div:isTony")

Sample http://jsfiddle.net/LMBgN/


至于效能,基本上就如官网文件所说。

基本上 "查询" ($(selector) 或 $parent.find(selector)) ,
能不用虚拟 selector 就不该用;

而"过滤" ($set.filter(selector) or $set.is(selector )) 就很适合用。:)


文末的文末,补个题外话,我一直很讶异的是,
jQuery mobile 是用这么髒的方式,来实作他们需要的内部用 pseudo selector。:P

有时候看看别人的实作,想想背后的理由,也是蛮有意思的。:P

// Monkey-patching Sizzle to filter the :jqmData selectorvar oldFind = $.find,jqmDataRE = /:jqmData\(([^)]*)\)/g;$.find = function( selector, context, ret, extra ) {selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );return oldFind.call( this, selector, context, ret, extra );};

这篇因为想睡了讲的比较乱,有兴趣/疑问欢迎发问。:)


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章