CSS selector (jquery的源码分析,修改)
- 在jquery.fn.init()中,我们对这句jQuery(context).find(selector)没有深入去分析,在使用$()时候,大部分时间都是在使用这句来完成功能的。它就是调用CSS Selector到Dom树去查找和相符CSS语法的元素节点(集)。jQuery名字中query的意义就体现在这里。
- 根据符合CSS语法的字符串,它是怎么到DOM文档树去找到符合条件的元素呢?无论怎么解析这个字符串,它总得有调用最原始的函数来完成功能,这些函数是什么?
- 在没有分析Selector源码之前,说来也不会相信功能强大的selector是建立在元素的getElementsByTagName,getElementById直接引用和元素的childNodes firstChild、lastChild、nextSibling、parentNode、previousSibling等间接引用这些函数的使用。
- 在分析源码之前,我们得了解一下CSS selector,它可以粗略分成几类基本的类型:ID选择器(#id),Class选择器(.class),类型(type)选择器(p),Combinators,属性(Attribute)选择器,Pseudo Classes选择器等。这些都是单一的选择器,可以在应用中把它们组合起来,如:div#id, div:last-child。
- 我们先分析一个例子:span[class=’test’],它是属性选择器,我们一般把它做一个整体来理解:在Dom文档树中找到其class等于’test’的且标签为span的元素。这是一步到位,直接从dom文档树查找(select)所需要的元素。
- 其实我们更细一点分析,完成可以把这个字符串拆分成两步走,第一步是根据document.getElementsByTagName(tagName)取得span的元素集,第二步是再根据其class是否等于test来进行判断,把不等于的元素从结果集中去掉。
- 不光是属性选择器,对所有的复合的选择器,都可以根据这种细粒度来拆分选择器,接下来就是分别是每个小部分进行操作。如果这样,我们就能把CSS selector分成两大类,第一类是是选择(selector),从根据给定的selector从Dom树找到相关的元素节点放到结果集中来。第二类是筛选(filter)。在结果集中判断该元素还满足表达式。
- 这样一来,就完全可以JS的原有直接或间接对Dom元素进行引用的方法,如div#id就可以变成先找到div的元素,之后就判断其元素的id是否等于id,如果不等于就从结果集中删除掉。而对于div[id=’bar’]也是一样,可以看出div#id与div[id=’id’]是一样的操作。对于div.class也可以转换到属性中去操作。
- 现在要做的事就分析那些基本选择器是完成选择(selector)还是筛选(filter)的工作。我们把类型选择器,Combinators统一称为元素选择器,其形式如下:*,E F,E~F,E+F,E>F,E/F,E。这些一定是从dom树中选择元素。
- 对于ID选择器和Class选择器,它们也可以在selector字符串的起始位置,说明他们也可以完成选择(selector)功能。当它们不在起始位置上,如div#id,div.class那他们就是筛选器。我们可以变通一下统一起来,对于ID选择器可以采用document.getElementById来直接引用。但是对于.class就不一样了,它没有相对的函数。这种情况下,我们可以把它变成*.class。*能取得一个范围内的所有的元素,然后再进行筛选。这样对于ID选择器,它有专门的函数来处理,是选择(selector)器。对Class选择器,它是筛选器,特殊情况就是对于*标签取到元素集进行筛选。
- 其它如属性(Attribute)选择器,Pseudo Classes都是筛选器,只能附在一个元素或多个元素后面。,特殊情况就是对于*标签取到元素集进行筛选。
- 看一个:div.foo:nth-child(odd)[@foo=bar]#aId > a的例子:
- 第一步:找到div type选择器的所有元素。
- 第二步:在这些元素中找到class为foo的元素。如果没有就根本不要去分析下面的字符。
- 第三步:根据:nth-child(odd)找到元素的子孩子元素为偶数的的元素。进一步筛选了。
- 第四步:找到[],就是属性筛选器了,根据foo属性值bar来判断集合中还有那些满足条件。
- 第五步:在这些元素集合,找到id=aId的元素,进一步筛选。
- 第六步:在这些元素集合中找到所有元素对应的子元素类型为a的所有子元素。
- 结果:我们可以看到,现在元素是子元素的集合。
-
- 讲完了通版的大道理,接下来就是来看看jQuery是怎么实现的:
-
-
- find : function(selector) {
- var elems = jQuery.map(this, function(elem) {
- return jQuery.find(selector, elem); });
- return this.pushStack(/[^+>] [^+>]/.test(selector) ? jQuery
- .unique(elems) : elems);
- },
- 这是jquery.fn.init调用的find方法。它只是完成之本本对象集合中每一个元素都调用jQuery.find(selector, elem)方法,组合成新unique集合后构建新jquery对象。最重要的实质性的工作在jQuery.find(selector, elem)完成:
-
- find : function(t, context) {
- if (typeof t != "string")return [t];
- if (context && context.nodeType != 1 && context.nodeType != 9)
- return [];
- context = context || document;
-
- var ret = [context], done = [], last, nodeName;
-
-
-
-
-
- while (t && last != t) {
- var r = [];
- last = t;
- t = jQuery.trim(t);
-
- var foundToken = false, re = quickChild,
- m = re.exec(t);
-
-
-
-
- if (m) { ①
- nodeName = m[1].toUpperCase();
-
-
-
-
- for (var i = 0;ret[i]; i++)
- for (var c = ret[i].firstChild;c; c = c.nextSibling)
- if (c.nodeType == 1&& (nodeName == "*" ||
- c.nodeName.toUpperCase() == nodeName))
- r.push(c);
- ret = r;
- t = t.replace(re, "");
-
-
-
-
-
-
-
- if (t.indexOf(" ") == 0)continue;
- foundToken = true;
- }
- else {
- re = /^([>+~])\s*(\w*)/i;
- if ((m = re.exec(t)) != null) {
- r = [];
- var merge = {};
- nodeName = m[2].toUpperCase();
- m = m[1];
-
-
-
-
-
- for (var j = 0, rl = ret.length;j < rl; j++) {
-
- var n = (m == "~" || m == "+"
- ? ret[j].nextSibling: ret[j].firstChild);
- for (;n; n = n.nextSibling)
- if (n.nodeType == 1) {
- var id = jQuery.data(n);
-
- if (m == "~" && merge[id])
- break;
- if (!nodeName|| n.nodeName.toUpperCase() == nodeName) {
- if (m == "~") merge[id] = true;
- r.push(n);
- }
- if (m == "+") break;
- }
- }
- ret = r;
- t = jQuery.trim(t.replace(re, ""));
- foundToken = true;
- }
- }
-
-
- if (t && !foundToken) { ③
-
-
- if (!t.indexOf(",")) { ④
-
-
-
- if (context == ret[0]) ret.shift();
- done = jQuery.merge(done, ret);
- r = ret = [context];
- t = " " + t.substr(1, t.length);
- }
- else { ⑤
-
-
-
-
- var re2 = quickID;
- var m = re2.exec(t);
-
- if (m) {m = [0, m[2], m[3], m[1]];
- else { re2 = quickClass;
- m = re2.exec(t);
-
- m[2] = m[2].replace(/\\/g, "");
-
-
-
-
-
-
-
-
-
- var elem = ret[ret.length - 1];
- if (m[1] == "#" && elem && elem.getElementById
- && !jQuery.isXMLDoc(elem)) {
- var oid = elem.getElementById(m[2]);
-
-
-
- if ((jQuery.browser.msie || jQuery.browser.opera) && oid
- && typeof oid.id == "string" && oid.id != m[2])
-
- oid = jQuery('[@id="' + m[2] + '"]', elem)[0];
-
-
- ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3]))
- ? [oid]: [];
- }
- else {
-
-
-
-
-
- for (var i = 0;ret[i]; i++) {
-
-
-
-
-
-
-
-
-
- var tag = (m[1] == "#" && m[3] ? m[3] : (m[1] != ""
- || m[0] == "" ? "*" : m[2]));
-
-
- if (tag == "*"&& ret[i].nodeName.toLowerCase() == "object")
- tag = "param";
-
- r = jQuery.merge(r, ret[i].getElementsByTagName(tag));
- }
-
-
- if (m[1] == ".") r = jQuery.classFilter(r, m[2]); ⑦
-
- if (m[1] == "#") {
- var tmp = [];
- for (var i = 0;r[i]; i++)
- if (r[i].getAttribute("id") == m[2]) {
- tmp = [r[i]];
- break;
- }
- r = tmp;
- }
- ret = r;
- }
- t = t.replace(re2, "");
- }
-
- }
-
-
-
-
-
-
-
- if (t) {
- var val = jQuery.filter(t, r); ⑧
- ret = r = val.r;
- t = jQuery.trim(val.t);
- }
- }
-
-
-
-
-
-
-
- if (t) ret = [];
-
-
-
- if (ret && context == ret[0])
- ret.shift();
-
- done = jQuery.merge(done, ret);
-
- return done;
- },
- 上面find的实现的代码有点长。其实分析起来也不是很难。①②③④⑤处两个if-else是元素选择器,针对>/+~ F或#id,.class tagName,div#id这样的选择器进行查找元素,构成结果集。⑥实现的就是分析它之后的属性选择器进行筛选。这段代码说白就是select与Filte之间的交互分析CSS selector的字符串进行查找或筛选的过程。
- 具体的细节在代码中有分析。在⑦处它采用先找到所有元素然后对每个元素进行class的判断来分析如.class这样selector。因为它在起始位说明是查找器。但是这里对这个最小的单元部分,我们还可能两个部分,找到所有元素,然后一个个排查,排查就是采用了jQuery.classFilter(r, m[2]):
- classFilter : function(r, m, not) {
- m = " " + m + " ";
- var tmp = [];
- for (var i = 0;r[i]; i++) {
- var pass = (" " + r[i].className + " ").indexOf(m) >= 0;
- if (!not && pass || not && !pass)
- tmp.push(r[i]);
- }
- return tmp;
- },
- jQuery.classFilter是比较简单。在find()的第二个部分是筛选,它调用jQuery.filter(t, r)来完成功能:
-
-
-
- filter : function(t, r, not) {
- var last;
- while (t && t != last) {
- last = t;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- var p = jQuery.parse, m;
- for (var i = 0;p[i]; i++) {
- m = p[i].exec(t);
- if (m) {
- t = t.substring(m[0].length);
- m[2] = m[2].replace(/\\/g, "");
- break;
- }
- }
-
- if (!m) break;
-
-
- if (m[1] == ":" && m[2] == "not")
-
- r = isSimple.test(m[3])
- ? jQuery.filter(m[3], r, true).r: jQuery(r).not(m[3]);
-
-
- else if (m[1] == ".")
- r = jQuery.classFilter(r, m[2], not);
-
- else if (m[1] == "[") {
- var tmp = [], type = m[3];
-
- for (var i = 0, rl = r.length;i < rl; i++) {
-
-
- var a = r[i], z = a[jQuery.props[m[2]] || m[2]];
-
-
-
-
-
-
- if (z == null || /style|href|src|selected/.test(m[2]))
- z = jQuery.attr(a, m[2]) || '';
-
-
-
-
- if ( (type == "" && !!z
- || type == "=" && z == m[5]
- || type == "!=" && z != m[5]
- || type == "^=" && z&& !z.indexOf(m[5])
- || type == "$=" && z.substr(z.length - m[5].length) == m[5] || (type == "*=" || type == "~=")&& z.indexOf(m[5]) >= 0
- )
- ^ not)
- tmp.push(a);
- }
- r = tmp;
-
- }
-
-
-
- else if (m[1] == ":" && m[2] == "nth-child") {
- var merge = {}, tmp = [],
-
-
-
-
-
- test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3] == "even" && "2n"
- || m[3] == "odd" && "2n+1" || !/\D/.test(m[3]) && "0n+"
- + m[3] || m[3]),
-
-
- first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
-
-
- for (var i = 0, rl = r.length;i < rl; i++) {
- var node = r[i], parentNode = node.parentNode,
- id = jQuery.data(parentNode);
-
- if (!merge[id]) {
- var c = 1;
- for (var n = parentNode.firstChild;n; n = n.nextSibling)
- if (n.nodeType == 1)n.nodeIndex = c++;
- merge[id] = true;
- }
-
- var add = false;
-
-
- if (first == 0) {
- if (node.nodeIndex == last)
- add = true;
- }
-
-
-
-
- else if ((node.nodeIndex - last) % first == 0
- && (node.nodeIndex - last) / first >= 0)
- add = true;
-
- if (add ^ not) tmp.push(node);
- }
-
- r = tmp;
-
- }
- else {
- var fn = jQuery.expr[m[1]];
-
- if (typeof fn == "object")
- fn = fn[m[2]];
-
-
-
- if (typeof fn == "string")
- fn = eval("false||function(a,i){return " + fn + ";}");
-
-
- r = jQuery.grep(r, function(elem, i) {
- return fn(elem, i, m, r);
- }, not);
- }
- }
- return {
- r : r,
- t : t
- };
- },
- jQuery.filter完成分析属性([])、Pseudo(:),class (.),id(#),的筛选的功能。从给定的集合中筛选出满足上面四种筛选表达式的集合。针对于find()。这个filter完成不表明整个selector的分析完成了。还会交替地通过查找器来查找或通过该函数来筛选。对于单独使用这个函数,表达式中就不应该含有查找的selector表达式了。筛选是根据[、:、#、.这四个符号来做为筛选器的分隔符的。
- class筛选器是通过classFilter来完成。它还把Pseudo中:not、:nth-child单独从Pseudo类的中单独提起出来处理。对于[的属性筛选器,实现起来也是很简单。除去这些,它还调用jQuery.expr[m[1]];来处理Pseudo类,而jQuery还做了扩展。jQuery.expr中的Pseudo类有以下几个部分:
- 如果想详细了解这样怎么用吧,推荐由一揪整理编辑jquery的中文文档。,
-
- 对于CSS selector,尽管多次分析domQuery,和jquery Selector。尽管自己也吃透。但是感觉到写出来还是不到位。如果读者之前没有接受这方面。建议仔细分析上面的代码和注释。找一个复杂的例子,自行分析一下,或许可能弄懂selector的设计。
原文地址:https://www.cnblogs.com/rooney/p/1346449.html