介绍一些虚拟选择器,并且说明如何扩充虚拟选择器。 :)
基本上 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 );};
这篇因为想睡了讲的比较乱,有兴趣/疑问欢迎发问。:)