2.3.5 引用 DOM 元素
jQuery() 函数能够直接接受 HTML 字符串,并把它们转换为 DOM 结构,这是上一节中所讲解的利用 jQuery() 函数生成 DOM 元素。当然,我们也可以看到 jQuery() 函数还可以接收 DOM 元素、DOM元素集合、HTML标签或者 ID 值。下面我们就来分析 jQuery.fn.init() 构造器是如何把这些类型的参数转换为 DOM 元素的。
对于 HTML 标签来说,它使用 document.getElementsByTagName() 方法获取 DOM 元素集合。对于 ID 参数来说,它使用 document.getElementById() 方法获取特定的 DOM 元素。另外,还可以使用 DOM 元素的 childNodes、firstChild、lastChild、nextSibling、parentNode 和 previousSibling 的属性引用 DOM 节点。
既然说 DOM 元素能够通过 lastChild、parentNode 等属性引用节点,jQuery 对象又是 DOM 元素的集合,因此,jQuery 就可以考虑通过整合 DOM 元素的这些属性来获得其集合中所有元素各自引用的节点。把这些间接引用的节点组合起来又构成了新的 DOM 元素集合。下面我们就来分析 jQuery 是如何引用节点的。
// 通过 each() 方法为 jQuery 对象映射一组引用 DOM 节点的方法
jQuery.each({
parent: function(elem){return elem.parentNode;}, // 引用父节点
parents: function(elem){return jQuery.dir(elem, "parentNode");}, // 引用所有父节点
next: function(elem){return jQuery.nth(elem, 2, "nextSibling");},// 引用相邻的下一个 DOM 元素
prev: function(elem){return jQuery.nth(elem, 2, "previousSibling");}, // 引用相邻的上一个DOM元素
nextAll: function(elem){return jQuery.dir(elem, "nextSibling");}, // 引用所有后继DOM元素
prevAll: function(elem){return jQuery.dir(elem, "previousSibling");}, // 引用所有前继DOM元素
sibling: function(elem){return jQuery.sibling(elem.parentNode.firstChild, elem);}, // 引用相邻DOM元素
children: function(elem){return jQuery.sibling(elem.firstChild);},// 引用所有子元素
// 如果存在 iframe,则就是文档,或者所有子节点
// elem.contentDocument || elem.contentWindow.document iframe 的属性
// http://devoloper.mozilla.org/on/docs/XUL:iframe
contents: function(elem){return jQuery.nodeName(elem, "iframe")?elem.contentDocument ||
elem.contentWindow.document : jQuery.makeArray(elem.childNodes);}
}, 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 );
// 返回构建的 jQuery 对象
return this.pushStack(jQuery.unique( ret ), name, selector);
};
});
在上面的代码中,为 jQuery 对象绑定了一组方法,这些方法能够引用不同位置的节点,主要包括父节点、子节点和兄弟节点三类。
- 对于父节点引用来说,可以引用当前 DOM 元素的父亲节点,也可以获得所有父级节点,包括祖先节点。
- 对于子节点引用来说,就是可以引用所有直接的子节点,但不包括不相邻的后代节点。
- 对于兄弟节点引用来说,可以引用包括当前 DOM 元素的前或后相邻节点,也包括前后相近的所有元素。
// 从一个元素出发,检索某个方向上的所有元素
// 如可以把元素的 parentNode、nextSibling、previousSibling、lastChild、firstChild 属性作为方向
// 参数说明:
// elem 参数表示起点元素
// dir 参数表示元素的方向属性,如 parentNode、nextSibling、previousSibling、lastChild、firstChild
jQuery.dir = function(elem, dir){
var matched = [], cur = elem[dir];
// 逐级迭代访问,直到访问到 document 节点
while(cur && cur != document){
if (cur.nodeType == 1) // Element 类型
matched.push(cur);
cur = cur[dir]; // 向上一级传递节点
}
return matched;
};
dir 是 direction(方向) 一词的缩写,表示朝一个方向一直迭代到尽头。例如,parentNode 能够把父节点作为当前节点,再取其父节点,通过这种方式可以迭代到 document 对象为止。另外,对于 nextSibling、previousSibling、lastChild 和 firstChild 元素属性都具有方向性,因此只要获取元素具有 dir (方向) 特性的属性,就可以反复迭代读取。每循环一次都会把取到的元素保存起来。
所以说,dir() 函数对于检索 DOM 文档树中呈放射线性排列的元素来说,是非常有用的。但是如果要检索在某个方向上的第几个元素,如检索偶数序号位置的元素,就需要使用 nth() 函数。该函数的源代码如下所示。
// 从一个元素出发,检索某个方向上的第几个元素。参数 Result 是第几个
// 参数说明:
// cur 参数表示起点元素
// dir 参数表示元素的方向属性,如 parentNode、nextSibling、previousSibling、lastChild 和 firstChild
// result 参数表示级数,默认值为 1
// elem 参数是一个无用参数
jQuery.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;
};
jQuery.nth() 函数与 jQuery.dir() 函数在设计思路上是完全相同的,但是 jQuery.dir() 函数不包含自身,而 jQuery.nth() 函数可以包含自身,如果 jQuery.nth() 函数的参数 result 等于 1,则返回自身元素。如果没有找到元素则返回空,如 undifined 或 null 。
jQuery.sibling() 函数就比较简单,但是没有上面两个方法有用。它实现了从一个元素(包括自身)检索所有后续相邻元素,然后再从这个后续的相邻元素排除一个指定元素。例如:
// 从包含参数 n 的元素开始检索所有后续相邻元素,但不包含参数 elem 指定的元素
// 参数说明:
// n 参数表示起点元素
// elem 参数排除元素
jQuery.sibling = function(n, elem){
var r = [];
for( ; n; n = n.nextSibling){
if(n.nodeType == 1 && n!=elem)
r.push(n);
}
return r;
};