Jquery源码分析---构建Jquery的Dom元素
2010-09-14 13:36:44 来源:WEB开发网上面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 + " ";//这里的做法就是怕出现如”class12”这样的问题
var tmp = []; //如果传的m= class1,这个的条件是满足的,实际是不。
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)来完成功能:
//根据CSS selector表达式查找集合中满足该表达式的所有元素
//还可以根据not来指定不满足CSS selector表达式元素集
filter : function(t, r, not) {
var last;
while (t && t != last) {// t存在,且改变
last = t;
// Match: [@value='test'], [@foo]
// 1、^([) *@?([w:-]+) *([!*$^~=]*) *('?"?)(.*?)4 *]/,
// Match: :contains('foo')
// 2、^(:)([w-]+)("?'?(.*?((.*?))?[^(]*?)"?'?)/,
// Match: :even, :last-child, #id, .class
// 3、new RegExp("^([:.#]*)(" + chars + "+)")],
//这里可以看出我们直接调用filter的时候的selector如不是筛选器的话,
//那就不进行筛选了,这里的selector语法如[@value='test'], [@foo]、
//:contains('foo'),:even, :last-child, #id, .class的形式
//可以是上面这几种形式的组合,但不能包括元素选择器。
//而且复合的形式中间不能采用空格隔开,如[@value='test']#id.class可行的。
var p = jQuery.parse, m;
for (var i = 0;p[i]; i++) {// 找到与jQuery.parse中regexp相配的
m = p[i].exec(t);
if (m) {
t = t.substring(m[0].length);//删除处理过的字符部分
m[2] = m[2].replace(//g, "");// 有可能会有没有转换的去掉
break;
}
}
// 与上面三种的regexp都不相配
if (!m) break;
//处理 :not(.class)的形式,返回集合中不包含.class的其它的元素
if (m[1] == ":" && m[2] == "not")
// 性能上优化 m[3]是.class经常出现
r = isSimple.test(m[3])// isSimple = /^.[^:#[.]*$/
? jQuery.filter(m[3], r, true).r: jQuery(r).not(m[3]);
//处理.class的过滤。只要看看m[2]这个class是不是被集合中元素的class包含。
else if (m[1] == ".")// 性能上优化考虑
r = jQuery.classFilter(r, m[2], not);
//处理属性过滤。如[@value='test']形式的属性选择
else if (m[1] == "[") {
var tmp = [], type = m[3];// 符号,如=
for (var i = 0, rl = r.length;i < rl; i++) {
//jQuery.props[m[2]]进行tag中属性名和对应的元素的属性名转换,
//因为tag中属性名是元素中简写,z取到 元素的属性值
var a = r[i], z = a[jQuery.props[m[2]] || m[2]];
//直接取元素的属性值,没有取到,说明有的浏览器不支持这种方法
//进一步尝试采用jQuery.attr来进行非标准的兼容取属性值。
//就算是取到了值,但对于属性名为style|href|src|selected,
//它们不能直接取值,要进行特殊的处理,这个在jQuery.attr进行。
//其实这里可以直接采用jQuery.attr(a, m[2]),一步到位。
if (z == null || /style|href|src|selected/.test(m[2]))
z = jQuery.attr(a, m[2]) || '';// 几个特殊的处理
//如果属性选择器满足这
//[foo],[foo=aa][foo!=aa][foo^=aa][foo$=aa][foo~=aa]
//这几种方式之一,这个元素就可能通过。即满足条件。m[5]属性值。
if ( (type == "" && !!z//[foo]
|| type == "=" && z == m[5]//[foo=aa]
|| type == "!=" && z != m[5]//[foo!=aa]
|| type == "^=" && z&& !z.indexOf(m[5])//[foo^=aa]
|| type == "$=" && z.substr(z.length - m[5].length) == m[5] || (type == "*=" || type == "~=")&& z.indexOf(m[5]) >= 0
)
^ not)
tmp.push(a);
}
r = tmp;
}
//处理:nth-child(n+1)。其实这里也改变了结果集,
//不过这里是采用的是间接引用的方式,只要知道元素就可以了,
//不需要dom树去查找。因为它要解析参数中的表达式
else if (m[1] == ":" && m[2] == "nth-child") {// 性能考量
var merge = {}, tmp = [],
// 分析:nth-child(n+1)中的参数,这里支持
//'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'几种形式
//test[1]="-或空",test[2]="n前面的数或空",test[3]="n后面的数或空"
//这样把参数分成三个部分:1是负号的处理,2是xn中的x处理,3是n-x中-x的处理
//3中的是带有符号的。也就是+或-。
test = /(-?)(d*)n((?:+|-)?d*)/.exec(m[3] == "even" && "2n"
|| m[3] == "odd" && "2n+1" || !/D/.test(m[3]) && "0n+"
+ m[3] || m[3]),
// 计算(first)n+(last)
first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
//找到符合(first)n+(last)表达式的所有子元素
for (var i = 0, rl = r.length;i < rl; i++) {
var node = r[i], parentNode = node.parentNode,
id = jQuery.data(parentNode);//为该元素parentNode分配了一个全局的id
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;//初始化add的标记
//常数的形式,如1,2等等,当然还要判断元素的序号和这个数是否相等。
if (first == 0) {// 0不能作除数
if (node.nodeIndex == last)
add = true;
}
// 处理3n+2这种形式同时表达式要大于0
//当前的子节点的序号要满足两个条件:
//1、其序号进行first求余的结果=last.
//2、其序号要大于last。对于-n的形式,要大于-last.
else if ((node.nodeIndex - last) % first == 0
&& (node.nodeIndex - last) / first >= 0)
add = true;
if (add ^ not) tmp.push(node);
}
r = tmp;
}
else {// 根据m[1]m[2]在Query.expr找到对应的处理函数
var fn = jQuery.expr[m[1]];
//支持一个符号(如:last)后的方法名与函数的对应
if (typeof fn == "object")
fn = fn[m[2]];
//支持更简短的string来代替jQuery.expr中的funciton。
//这里没有用到。
if (typeof fn == "string")
fn = eval("false||function(a,i){return " + fn + ";}");
// 执行处理函数fn过滤。对于r中每个元素,如果fn返回的结果为true,保留下来。
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类有以下几个部分:// Position Checks、// Child Checks、// Parent Checks、// Text Check、// Visibility、// Form attributes、// Form elements、// :has()、// :header、// :animated。也就是说在CSS selector中,我们可以采用Pseudo类提供上面这些类别的方法来进行筛选。其代码就是一些判断,不作分析。
如果想详细了解这样怎么用吧,推荐由一揪整理编辑jquery的中文文档。,
对于CSS selector,尽管多次分析domQuery,和jquery Selector。尽管自己也吃透。但是感觉到写出来还是不到位。如果读者之前没有接受这方面。建议仔细分析上面的代码和注释。找一个复杂的例子,自行分析一下,或许可能弄懂selector的设计。
文章来源:http://jljlpch.javaeye.com/category/37744
赞助商链接