Jquery源码分析---构建Jquery的Dom元素
2010-09-14 13:36:44 来源:WEB开发网find : function(t, context) {
if (typeof t != "string")return [t];// 快速处理非字符表达式
if (context && context.nodeType != 1 && context.nodeType != 9)
return [];// 确保context是DOM元素或document
context = context || document;// 缺省的context
// 初始化,ret:result, done:已经完成,last:上一次的t,nodeName:节点名,
var ret = [context], done = [], last, nodeName;
//这里就是把复合选择器的字符串从左到右取最小单元的选择进行分析操作
//分析操作完之后就这个分析过的字符串部分给删除,
//然后循环分析接下来的剩余的部分。直到字符串为空。
//这里的最小单元指如#id,~F(+F,>F),.class,[id='xx'],F,:last()之类
while (t && last != t) {// t存在,且变化
var r = []; // ret的tempValue
last = t; // last:上一次的t
t = jQuery.trim(t);// 去首尾空格
var foundToken = false, re = quickChild, // 以>开头的regexp
m = re.exec(t);
//这一部分处理了>,+,~的元素选择器。当然有的后代,有的兄弟选择的。
// 首先判断是不是以>开头,因为每次处理都处理都删除分析过的字符串部分
//这里可以看作是>作为找到tagName元素的子节点们的标记
if (m) { ①
nodeName = m[1].toUpperCase();//tagName
//在结果集中(第一次是默认是给定的context)找到满足的tagName元素的所有子节点。
//两个循环,第一是对结果集中每个元素进行,第二个是对每个元素中每个子元素节点。
//找到结果集中所有的元素的所有子元素的集合。
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, "");// remove已经处理过的字符串部分
//对于E (F,>F,+F etc)的形式,这里跳过后面的代码又回到while处执行。
//但是在while处之后会把这个空格去掉。好像没有进行操作。这里变化了是ret。
//无论后面是怎样的最小单元选择器,都是要根据这个个ret中的元素来进行操作。
//如果是tagName,那么就是转4处执行ret[i].getElementsByTagName().
//如果是>tagName,就执行1处的代码,其它的省略,
//可见每个最小单元之后都可以是任意的空格分隔。
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];// 符号,如+,~
// 如果selector字符串的匹配+或~(子元素),
//在结果集中所有元素中找到其兄弟元素节点。
//不同的+找的是后续的第一个兄弟元素,
//而~是找到所有的后续的兄弟元素节点。
//之后把找到的所有的元素放到结果集合中。
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);// 为n元素生成全局唯一的id
if (m == "~" && merge[id])// 保证ret中元素不重复
break;// nextSibling会循环到第一个节点?
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) { ③
//这里的意思是在开始的位置找到,号,说明一个selector已经完成了,那么
//结果集就要存到已经完成的集合中。结果集也应该初如化。
if (!t.indexOf(",")) { ④
//说明运行到这里的时候,还是单个selector的字符串分析是刚刚开始
//因为>~+不可能得到ret[0]元素等于元素的自身。如果等于的话,
//那就清除出ret,因为接下来就要把ret结果集中的元素存入done中
if (context == ret[0]) ret.shift();
done = jQuery.merge(done, ret);// ret的其它元素放入done
r = ret = [context];// 重新初始化
t = " " + t.substr(1, t.length); //把,采用空格代替。
}
else { ⑤
// 说明这一个selector部分还没有完成,同时还没有找到元素
// 或者是 >F的后面用空格来分隔。
//* qId:^((?:[w*_-]|.)+)(#)((?:[w*_-]|.)+)
// * qclass:^([#.]?)((?:[w*_-]|.)*)
var re2 = quickID;// 如(.)nodeName#idName
var m = re2.exec(t);// 找到第一个相配的
if (m) {m = [0, m[2], m[3], m[1]];// m=[0,#,idName,nodeName]}
else { re2 = quickClass;// #nodeName,.className
m = re2.exec(t);// m=[all,#,idName]}
m[2] = m[2].replace(//g, "");// 去除转义字符
//取数组的最后一个元素,其实就是为了取到是不是为document,
//因为只有document才有getElementById,为什么不直接采用
//document呢?难道document的查找会比先根据element.
//getElementsByTagName找到元素下面的tagname的相符的
//集合然后采用id属性去判断对比来得更慢吗?不一定?对于大的Dom树,
//而element的范围又很小的话,可能会慢一些。
//不过由于这里还要通过属性选择器来进行验证进行验证,一般来说
//element.getElementsByTagName会快一点。
var elem = ret[ret.length - 1];
if (m[1] == "#" && elem && elem.getElementById
&& !jQuery.isXMLDoc(elem)) {
var oid = elem.getElementById(m[2]);
// 回测元素的ID的确存在,在IE中会取name属性的值,同时在form 元素中
// 会选中在form中元素的name属性为id的元素。
if ((jQuery.browser.msie || jQuery.browser.opera) && oid
&& typeof oid.id == "string" && oid.id != m[2])
//通过属性选择器来进行验证。
oid = jQuery('[@id="' + m[2] + '"]', elem)[0];
// 回测元素的node Name是否相同,如div#foo,可以提交效率
ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3]))
? [oid]: [];
}
else {
//这里处理了#id,.class tagName,div#id四种形式
//这里我们还可以看出E F形式。并没有特殊的处理。就是采用了
//E.getElementsByTagName(F)就可以了。
//这个就能取后元素的所有后代为F和F的元素节点。
//和F使用是统一的起来。因为E都是结果集。
for (var i = 0;ret[i]; i++) {
//因为m有两种情况:[0,#,idName,nodeName]、[all,#/.,idName/class/tagName]
//这里就是根据这两种情况来取得tagName,m[1] == "#" && m[3]
//为真,说明是第一种,取nodeName。如果m[1] == "#" && m[3]为假
//说明m[1] <> "#"||!m[3],而m[1] != ""说明只能是第二个数组中的.或#
//说明了对于#nodeName,.className采用element.getElementsByTagName(*).
//当m[1] == "",说明是一个E 元素选择器,它不带任何的前缀。如:p。这个时候
//m[2]是tagName.
//m[0] == "" ,只能指第二个数组中的。它=="",说明没有找到符合qclass的regExp.
//其它的情况都不会为"",它为"",!m[1],!m[2]也都为true.
var tag = (m[1] == "#" && m[3] ? m[3] : (m[1] != ""
|| m[0] == "" ? "*" : m[2]));// 分情况取tagName
//*的情况下,对于object标签转换为param标签进行操作。
if (tag == "*"&& ret[i].nodeName.toLowerCase() == "object")
tag = "param";// Handle IE7 being really dumb about <object>s
//把结果集合中第一个元素的getElementsByTagName存入到临时的结果集中。
r = jQuery.merge(r, ret[i].getElementsByTagName(tag));
}
//class选择器的话,就根据class属性在找到结果集合中过滤
if (m[1] == ".") r = jQuery.classFilter(r, m[2]); ⑦
//id选择器的话,就根据id属性在找到结果集合中过滤
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, "");
}
}
//这个时候已经找到结果的集合,对于如CSS Selector为:hidden的属性筛选器,
//它的集合就是context的下面的所有元素节点。也就是说上面的
//代码无论如何都能找到元素的集合。这个集合可能是>/+~ F
//或#id,.class tagName,div#id,对于不满足这些条件的,就采用
//context.getElementsByTagName(*)要取得其下所有的元素
//确保接下来的过滤(筛选)
if (t) {// 根据余下的selector,对找到的r集合中的元素进行过滤 ⑥
var val = jQuery.filter(t, r); ⑧
ret = r = val.r;
t = jQuery.trim(val.t);// 去首尾空格
}
}
//如果还会有t存在说明一个问题:last == t
//也就是上一次的过程中没有进行成功的解析一个最小单元的选择器
//原因是输入的 t 字符串有语法上的错误。如果是采用,分隔的多选择器
//那么就是当前及之后的选择器不分析。完成的done就是之前的结果集。
//觉得这样处理不好,很多时间我们都会写错CSS selectror,不报错,
//对于调试发现问题特难。
if (t) ret = [];
//出现这种情况说明运行到这里的时候,还是单个selector的字符串分析是刚刚开始
//如果等于的话,那就清除出ret,因为接下来就要把ret结果集中的元素存入done中
if (ret && context == ret[0])
ret.shift();// 去掉根上下文
done = jQuery.merge(done, ret);// 合并
return done;
},
更多精彩
赞助商链接