如何找到Dom元素

3、如何找到Dom元素
                                                    prk/彭仁夔       08-08-19
在jQuery.fn.init函数中,最终的结果是把Dom元素放到jQuery对象的集合,我们可以传入单个Dom元素或Dom元素集合直接把其存到jQuery对象的集合。但是如果第一个参数是string类型的话,如#id就要把Dom文档树去查找。对于html的片断就得生成Dom元素。我们再进一步,传入的单个Dom元素或Dom元素集合参数又是从那里来的?我们可以通过Dom元素的直接或间接的查找元素的方式。
这一部分首先分析如何从html的片断就得生成Dom元素,然后分析jQuery是如何通过直接或间接的方式在在Dom树中找到dom元素,第三就是分析基于CSS1~CSS3的CSS selector。

3.1生成Dom元素
Init方法中通过jQuery.clean([match[1]], context);来实现把html片断转换成Dom元素,这是一个静态方法:
// 把html转换成Dom元素,elems多个html string 的数组
clean : function(elems, context) {
	  var ret = [];
	  context = context || document;//默认的上下文是document
	  //在IE中!context.createElement行不通,因为它返回对象类型
	  if (typeof context.createElement == 'undefined')
			//这里支持context为jQuery对象,取第一个元素。
		context = context.ownerDocument || context[0]
						&& context[0].ownerDocument || document;

	jQuery.each(elems, function(i, elem) {
// 把int 转换成string的最高效的方法
		if (typeof elem == 'number')elem += '';
		if (!elem)	return;// 为'',undefined,false等时返回
		if (typeof elem == "string") {// 转换html为Dom元素
		// 修正 "XHTML"-style 标签,对于如<div/>的形式修改为<div></div>
		    //但是对于(abbr|br|col|img|input|link|meta|param|hr|area|embed)
//不修改	 。front=(<(\w+)[^>]*?)
elem = elem.replace(/(<(\w+)[^>]*?)\/>/g,				     function(all, front, tag) {	return            tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)? all: front + "></" + tag+ ">";}	);
		// 去空格,否则indexof可能会出不能正常工作
		var tags = jQuery.trim(elem).toLowerCase(), 
		div = context.createElement("div");//在上下文中创建了一个元素<div>
		// 有些标签必须是有一些约束的,比如<option>必须在<select></select>中间
		// 下面的代码在大部分是对<table>中子元素进行修正。数组中第一个元素为深度			var wrap = 
//<opt在开始的位置上(index=0)就返回&&后面的数组,这是对<option>的约束
!tags.indexOf("<opt")&& [1, "<select 
multiple='multiple'>","</select>"]
	//<leg 必须在<fieldset>内部
	|| !tags.indexOf("<leg")&& [1, "<fieldset>", "</fieldset>"]
	//thead|tbody|tfoot|colg|cap必须在<table>内部
	||  tags.match(/^<(thead|tbody|tfoot|colg|cap)/)
&& [1, "<table>", "</table>"]
     //<tr在<tbody>中间
	|| !tags.indexOf("<tr")&& [2, "<table><tbody>", "</tbody></table>"]
	 //td在tr中间
	 (!tags.indexOf("<td") || !tags.indexOf("<th"))&& [3, 
"<table><tbody><tr>","</tr></tbody></table>"]
	 //col在<colgroup>中间
	|| !tags.indexOf("<col")&& [2, 
"<table><tbody></tbody><colgroup>","</colgroup></table>"]
	 //IE中 link script不能串行化 ?
	|| jQuery.browser.msie&& [1, "div<div>", "</div>"]
	 //默认不修正
	|| [0, "", ""];

	// 包裹html之后,采用innerHTML转换成Dom
	div.innerHTML = wrap[1] + elem + wrap[2];

	while (wrap[0]--)
	// 转到正确的深度,对于[1, "<table>","</table>"],div=<table>
		div = div.lastChild;

	// fragments去掉IE对<table>自动插入的<tbody>
	if (jQuery.browser.msie) {								
	// 第一种情况:tags以<table>开头但没有<tbody>。在IE中生成的元素中可能会自动
// 加的<tbody> 第二种情况:thead|tbody|tfoot|colg|cap为tags,
// 那wrap[1] == "<table>" .tbody不一定是tbody,也有可能是thead等等
	var tbody = !tags.indexOf("<table")&& tags.indexOf("<tbody") < 0
				 ? div.firstChild&& div.firstChild.childNodes 
				 : wrap[1] == "<table>"&& tags.indexOf("<tbody") < 0
					   ? div.childNodes: [];
	// 除去<tbody>
	 for (var j = tbody.length - 1;j >= 0; --j)
		     if (jQuery.nodeName(tbody[j],
 "tbody")&&!tbody[j].childNodes.length)						    tbody[j].parentNode.removeChild(tbody[j]);

	//使用innerHTML,IE会去开头的空格节点的,加上去掉的空格节点
	if (/^\s/.test(elem))									 div.insertBefore(context.createTextNode
(elem.match(/^\s*/)[0]),div.firstChild);
	}
                     
	elem = jQuery.makeArray(div.childNodes);//elem从字符转换成了数组
}
      //采用===0,因为form,select都有length属性。这里主要是为了form,select进
//行下面的if else 处理。对于其它的length === 0的,也根本就不要加入到ret中。
if (elem.length === 0&& (!jQuery.nodeName(elem, "form")
				    && !jQuery.nodeName(elem, "select")))
						return;
   //不是(类)数组的形式的元素,或是form元素或是select元素(这两个可以看作类数组)
if (elem[0] == undefined|| jQuery.nodeName(elem, "form")|| elem.options)
		ret.push(elem);
else// 对于elems是array-like的集合
	ret = jQuery.merge(ret, elem);
});
   //上面的each中把有效的元素都加入到ret,现在只要返回就得到转换的Dom元素数组
return ret;
},
在上面的代码中,我们可以看出对于elems, context的参数的支持是多种形式的,elems可以为(类)数组的形式,还可以采用对象的形式。数组中的元素或对象的属性可以是混合形的,如string,ojbect,甚至(类)数组的形式。对于数字类型,会转换在string形,除string形之外的都放入返回的数组中,当然对于集合的形式,那就会取集合中每个元素。
对于string的形式就转换成Dom元素的形式,之后存到返回的数组中。这是这个函数的主要任务。对于把html转换成Dom元素,这里采用innerHTML把html挂到Dom文档树中。这样就转换成了Dom元素。
有些html标签片断是有约束的,比如<td>xx</td>,它必须存在table的tr中,也就是说在要进行html的标签片断的修正。这也是上面的代码处理的重点。
3.2间接引用Dom节点
 在init的参数中,我们可以直接输入Dom元素(集)的参数来构建jQuery对象,那么这些Dom元素(集)从那里来的呢?我们可以通过直接引用节点的方式如:document.getElementById,或getElementsByTag来引用Dom元素节点。我们还可以通过dom元素的我们可以通过元素的childNodes firstChild、lastChild、nextSibling、parentNode、previousSibling等间接来取得,等类似的属性来引用元素。因为这个不是直接在Dom中查找的,称为间接引用。
stChild、parentNode这些都是Dom元素的属性,jquery对象是Dom元素的集合。它也应该把这些间接引用的属性整合到jquery对象中来,这样就可以通过jquery对象来获得其集合中所有元素的各自的间接引用节点。把这些间接引用 的节点组合起来构成新的集合。集合中元素是jquery对象所有的元素的子节点或父节点之类的间接引用节点。
有了这些节点集合,可以作为jQuery.fn.init的参数传入。有人会问连这jQuery.fn.init都没有完成,怎么可能通过jquery对象而得到间接引用节点(集)呢? 
第一、因为jQuery.fn.init的参数可以有四个类型,我们完全可以通过String类型来先构建Jquery对象(html生成Dom对象,#id的getElementById直接引用,CSS Selector)。然后就可以把这个对象的间接引用到的元素节点作为参数传给下一个要构建的jquery对象。很多调用pushStack函数的用法就是这样的。
第二、如果硬是要不脱离jQuery来进行间接引用的话,jQuery提供几个静态方法:jQuery.dir、jQuery.nth、jQuery.sibling来完成间接引用。既然是间接引用,当然得先有一个dom元素的主体。直接引用也有主体,但是我们可以采用document来做为默认的。因为直接引用是查找的方式。
jQuery.each( {// 一组对元素的相关节点的操作,如父,子,兄节点等
	   parent : function(elem) {// 父亲节点
			return elem.parentNode;
		},
		parents : function(elem) {// elem的所有parentNode
			return jQuery.dir(elem, "parentNode");
		},
		next : function(elem) {// 元素的下一个兄弟
			return jQuery.nth(elem, 2, "nextSibling");
		},
		prev : function(elem) {// 前一个兄弟
			return jQuery.nth(elem, 2, "previousSibling");
		},
		nextAll : function(elem) {// 所有后继兄弟
			return jQuery.dir(elem, "nextSibling");
		},
		prevAll : function(elem) {// 所有前继兄弟
			return jQuery.dir(elem, "previousSibling");
		},
		siblings : function(elem) {// 所有兄弟
			return jQuery.sibling(elem.parentNode.firstChild, elem);
		},
		children : function(elem) {// 所有孩子
			return jQuery.sibling(elem.firstChild);
		},.. .. ..	},
 function(name, fn) {// 注册到jQuery对象中去,可以调用同名方法
			jQuery.fn[name] = function(selector) {
				var ret = jQuery.map(this, fn);// 每个元素都执行同名方法
				if (selector && typeof selector == "string")
					ret = jQuery.multiFilter(selector, ret);// 过滤元素集
			return this.pushStack(jQuery.unique(ret));// 构建jQuery对象
			};
		});
这段代码就是jQuery对象的间接引用节点的方法。它提供了父节点、子节点,兄弟节点三个方法的操作。对于父节点,可以可得到其当前的父亲节点,还可以得到所有父亲节点(包括祖先节点)。对于子节点,就是所有的直接的子节点(不包括其它的后代节点)。对于兄弟节点,有大(前面)兄弟节点(们)和小(后面)兄弟节点(们)之分。
接下就是把这八个节点注册到jQuery.fn(即jQuery对象)中的同名方法中。因为jQuery对象的Dom元素是集合,那么就得对集合中每个Dom元素都要进行单个元素相同的操作(上面8种的间接引用节点的Fn)。在组合中,有可能会有重复的dom元素,也还有可能用户会对这些间接引用节点进行其它的自定义的过滤。这里支持的过滤方式下一小节要分析的selector的过滤。最后构建一个新的jQuery对象。
其调用的方式如:$(‘p’). parents()。这个例子是所有p标签的所有父辈(包含祖先)节点的所组合而成的集合。这个集合有重复的元素可能性是很大。所以要jQuery.unique(ret)来进行过滤。
再举一个例子:$(‘p’). parents(“div”)。这个就是取所有所有父辈(包含祖先)节点中的Tagname为div的元素集合。这个就采用了CSS的过滤。最后还是要进行unique处理。
在上面的代码,我们也看到了几个静态间接引用节点的函数调用:
//从一个元素出发,取得一个方向上的所有元素,如parentNode、nextSibling、//previousSibling、lastChild,firstChild这样的元素的属性作为方向(dir).
dir : function(elem, dir) {
	var matched = [], cur = elem[dir];
	while (cur && cur != document) {
		if (cur.nodeType == 1)
			matched.push(cur);
		cur = cur[dir];
	}
	return matched;
},
取名dir,我们会想到Dos的Dir操作。其实我们解释为direction(方向)更好理解。它一直会朝着方向走到尽头。如parentNode的方向。它会把取的父节点作为当前节点再取其父节点。就是所有的父辈节点(们)。对于nextSibling、previousSibling、lastChild,firstChild都一样。只要取到的元素有dir这个方向(属性)的话,就一直取下去。每一步都会把取到的元素保存起来。这个函数是对于要取‘一条线’形式的Dom文档树的所有元素是有很有用的。从一个元素出发,其parentNode可以画一条线,lastChild也可以画一条线。当然nextSibling不也类外。
Dir是取某一个方向上所有的元素,有的时候,我们要取的是某一个方向上的第几个元素,如我们找到其爷爷节点。那就是某一个方向上的第二个元素。下面的nth就是实现这个功能。
// 取某个方向上的第几个元素。Result是第几个
nth : function(cur, result, dir, elem) {
	result = result || 1;
	var num = 0;
	for (;cur; cur = cur[dir])
		if (cur.nodeType == 1 && ++num == result)
			break;
	return cur;
},
Nth与dir在一个方向有点不同的地方,就是dir不包含自身,而nth包含自身,如果result是1的话,那是自身的元素。如果没有找到就返回空(有可能是undifined,或null)。
接下来的sibling就比较简单,但是用途也会少很多。它实现从一个元素(包括自身)找到其所有后续兄弟(们),然后从这个后续的兄弟(们)排除一个指的元素。
// 从包含n元素开始的所有后续兄弟,但不包含elem。
sibling : function(n, elem) {
	var r = [];
	for (;n; n = n.nextSibling) {
		if (n.nodeType == 1 && n != elem)
			r.push(n);
	}
	return r;
}
仔细想一下,没有什么好适用的场合,但是jquery.fn.siblings中的用法倒是最合适的用法。找到自己的所有兄弟。找自己的所有兄弟最好的方法当然是找到父亲节点,然后排除自身节点。采用childNodes再查找当前元素并去掉和这个操作差不多,效率应该会高点。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值