jQuery 源码分析(十九) DOM遍历模块详解
程序员文章站
2022-05-09 11:45:44
jQuery的DOM遍历模块对DOM模型的原生属性parentNode、childNodes、firstChild、lastChild、previousSibling、nextSibling进行了封装和扩展,用于在DOM树中遍历父元素、子元素和兄弟元素。 可以通过jQuery的实例来访问,方法如下: ......
jquery的dom遍历模块对dom模型的原生属性parentnode、childnodes、firstchild、lastchild、previoussibling、nextsibling进行了封装和扩展,用于在dom树中遍历父元素、子元素和兄弟元素。
可以通过jquery的实例来访问,方法如下:
- parent() ;获取匹配元素的父元素
- parents(selector) ;获取匹配元素的所有祖先元素 ;selector用于过滤查找的元素,如果为空则获取所有的祖先元素
- parentsuntil(until,selector) ;获得当前匹配元素集合中每个元素的祖先元素,直到遇到until元素
- next() ;获取匹配元素之后紧挨着的兄弟元素,没有则返回undefined
- nextall(selector) ;获取匹配元素之后紧挨着的所有兄弟元素
- nextuntil(until,selector) ;获取匹配元素之后紧挨着的所有兄弟元素,直到遇到until元素
- prev() ;获取匹配元素之前紧挨着的兄弟元素,没有则返回undefined
- prevall(selector) ;获取匹配元素之前紧挨着的所有兄弟元素
- prevuntil(until,selector) ;获取匹配元素之前紧挨着的所有兄弟元素,直到遇到until元素
- siblings(selector) ;获取匹配元素的所有兄弟元素,如果指定了selector则只获取该类型的元素
- children(selector) ;获取匹配元素的子元素,不包括文本节点和注释节点
- contents() ;获取匹配元素的子元素,包括文本节点和注释节点
举个栗子:
writer by:大沙漠 qq:22969969
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>document</title> <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script> </head> <body> <div> <h1>hello world!</h1> <p>123</p> <span>123</span> </div> <script> console.log( $('p').parent() ) //获取p元素的父元素 console.log( $('p').prev() ) //获取p元素之前紧挨的兄弟元素 console.log( $('p').next() ) //获取p元素之后紧挨的兄弟元素 console.log( $('p').siblings() ) //获取p元素的所有兄弟元素 console.log( $('div').children() ) //获取div的所有子元素,不包括文本节点和注释节点 console.log( $('div').contents() ) //获取div的所有子元素,包括文本节点和注释节点 </script> </body> </html>
输出如下:
对应栗子里的每一个输出,分别输出
- p元素的父节点
- p元素的上一个兄弟元素
- p元素的下一个兄弟元素
- p元素的所有兄弟元素
- div元素过滤掉文本节点和空白节点的子节点
- div元素包括文本节点和空白节点的子节点
返回的都是一个jquery对象,非常的好用,操作dom时都会用到这几个方法
源码分析
jquery对于dom遍历模块的实现是基于三个工具函数实现的
- $.dir(elem, dir, until) ;从elem元素开始,查找dir方向(可以是:parentnode、nextsibling或previoussibling)上的所有元素,如果在查找过程中遇到匹配参数until的元素,则查找终止
- $.nth(cur, result, dir, elem) ;从cur元素出发,查找dir方向(parentnode、nextsibling或previoussibling)上的第result个元素
- $.sibling(n, elem) ;负责查找一个元素之后的所有兄弟元素,包括起始元素,但不包括参数elem。n是查找的起始元素,包含在结果集中,elem是可选的dom元素,不包含在返回结果中。
举个栗子:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>document</title> <script src="http://libs.baidu.com/jquery/1.7.1/jquery.min.js"></script> </head> <body> <div> <h1>hello world!</h1> <p>123</p> <span>123</span> </div> <script> var h1 = document.getelementsbytagname('h1')[0], //分别获取h1、p、span元素的节点引用 p = document.getelementsbytagname('p')[0], span = document.getelementsbytagname('span')[0]; console.log( $.dir(h1,'parentnode') ) //打印h1的所有祖先节点 console.log( $.nth(h1,3,'nextsibling') ) //获取h1之后的第二个节点 console.log( $.sibling(h1,h1) ) //获取h1的所有兄弟元素,默认获取包括自身在内的元素,参数2也传入h1表示排除h1元素 </script> </body> </html>
输出如下:
输出三行,分别对应h1的所有祖先节点、h1之后的第二个子节点和h1的所有兄弟元素,和dom树中也是对应的。
对于$.dir、$.nth和$.sibling来说,它的实现如下:
jquery.extend({ dir: function( elem, dir, until ) { //从elem元素开始,查找dir方向(可以是:parentnode、nextsibling或previoussibling)上的所有元素,如果在查找过程中遇到匹配参数until的元素,则查找终止。 var matched = [], cur = elem[ dir ]; //cur表示下一个元素,根据参数dir不同,可能是父元素、前一个兄弟元素 或者 后一个兄弟元素 while ( cur && cur.nodetype !== 9 && (until === undefined || cur.nodetype !== 1 || !jquery( cur ).is( until )) ) { //如果下一个元素存在 且 不是document对象(nodetype等于9) 则继续判断括号里的语句,否则跳出循环 括号内:如果没有传入until参数,或者传入了until参数并且当前元素不是element节点(属性nodetype等于1) 或者 传入了until参数且当前参数是element节点且该元素不匹配参数until 则进行循环 if ( cur.nodetype === 1 ) { //如果cur是元素节点 matched.push( cur ); //存储到数组matched中 } cur = cur[dir]; //获取下一个方向上的元素 } return matched; //返回找到了的element节点数组。 }, nth: function( cur, result, dir, elem ) { //从cur元素出发,查找dir方向(parentnode、nextsibling或previoussibling)上的第result个元素 result = result || 1; var num = 0; //当前查找的元素序号 for ( ; cur; cur = cur[dir] ) { //从起始元素cur触发,沿着方向dir迭代遍历,只要cur元素存在则一直查找,直到查找该方向上的第result个element节点 if ( cur.nodetype === 1 && ++num === result ) { //如果cur是一个元素节点,则num+1,此时如果num等于result,则表示查找到该元素了,则跳出循环 break; } } return cur; //返回查找到的cur元素。 }, sibling: function( n, elem ) { //负责查找一个元素之后的所有兄弟元素,包括起始元素,但不包括参数elem。n是查找的起始元素,包含在结果集中,elem是可选的dom元素,不包含在返回结果中。 var r = []; for ( ; n; n = n.nextsibling ) { //从起始元素n触发,迭代遍历其后的所有兄弟元素 if ( n.nodetype === 1 && n !== elem ) { //如果该兄弟元素是元素节点且不等于参数elem则被放入数组r中。 r.push( n ); } } return r; //返回存放了找到的element节点的数组r } });
也就是对parentnode、childnodes、firstchild、lastchild、previoussibling、nextsibling这些原生dom操作的一些封装
我们通过jquery实例可以访问的方法的实现如下:
jquery.each({ parent: function( elem ) { //返回匹配元素的父元素 var parent = elem.parentnode; return parent && parent.nodetype !== 11 ? parent : null; //如果父元素存在且不是文档碎片元素,则返回该父元素,否则返回null }, parents: function( elem ) { //返回匹配元素的所有祖先元素 return jquery.dir( elem, "parentnode" ); }, parentsuntil: function( elem, i, until ) { //获得当前匹配元素集合中每个元素的祖先元素,直到遇到until元素 return jquery.dir( elem, "parentnode", until ); }, 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" ); }, nextuntil: function( elem, i, until ) { //获取匹配元素之后紧挨着的所有兄弟元素 ,直到遇到匹配元素until为止 return jquery.dir( elem, "nextsibling", until ); }, prevuntil: function( elem, i, until ) { //获取匹配元素之前紧挨着的所有兄弟元素 ,直到遇到匹配元素until为止 return jquery.dir( elem, "previoussibling", until ); }, siblings: function( elem ) { //获取匹配元素的所有兄弟元素 return jquery.sibling( elem.parentnode.firstchild, elem ); }, children: function( elem ) { //获取匹配元素的子元素 return jquery.sibling( elem.firstchild ); }, contents: function( elem ) { //获取匹配元素的子元素,包括文本节点和注释节点 return jquery.nodename( elem, "iframe" ) ? elem.contentdocument || elem.contentwindow.document : jquery.makearray( elem.childnodes ); } }, function( name, fn ) { //模板函数,用于调用对应的遍历函数查找dom元素,然后执行过滤、排序、和去重操作 name是每个函数名,fn是对应的值(也就是函数) jquery.fn[ name ] = function( until, selector ) { //定义模版函数,真正是在这里定义的,它接收两个参数 until:选择器表达式,用于指示查找停止的位置。selector:选择器表达式,用于过滤找到的元素 var ret = jquery.map( this, fn, until ); //调用方法jquery.map()遍历当前匹配元素集合,对每个元素调用遍历函数fn,并将遍历函数fn的返回值放入一个新的数组ret中。 if ( !runtil.test( name ) ) { //如果遍历函数名不以'until'结尾的,则最多只有一个参数 selector = until; //修正参数selector为until } if ( selector && typeof selector === "string" ) { //如果设置了参数selector 且selector是一个字符串 ret = jquery.filter( selector, ret ); //则调用jquery.filter()对数组ret中的dom元素逐个过滤,最终只保留匹配选择器表达式selector的元素。 } ret = this.length > 1 && !guaranteedunique[ name ] ? jquery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { //排序 ret = ret.reverse(); } return this.pushstack( ret, name, slice.call( arguments ).join(",") ); //创建一个jquery对象并返回 }; });
实现就是通过$.each()遍历一个对象,依次执行参数2这个函数,该函数会在$.fn上依次挂载每个接口,使用$.map()工具方法它会依次执行fn函数,进行一些过滤去重后最后调用$.pushstack()将数组转换为一个jquery对象,这里理解起来有点难度,需要多理一下代码。
上一篇: ProgressBar及其子类
推荐阅读
-
jQuery 源码解析(二十九) 样式操作模块 尺寸详解
-
jQuery 源码分析(十三) 数据操作模块 DOM属性 详解
-
jQuery 源码解析(二十三) DOM操作模块 替换元素 详解
-
jQuery 源码分析(十二) 数据操作模块 html特性 详解
-
jQuery 源码解析(二十四) DOM操作模块 包裹元素 详解
-
jQuery 源码分析(十九) DOM遍历模块详解
-
jQuery 源码分析(二十一) DOM操作模块 删除元素 详解
-
jQuery 源码分析(十五) 数据操作模块 val详解
-
jQuery源码分析(九) 异步队列模块 Deferred 详解
-
jQuery 源码分析(十六) 事件系统模块 底层方法 详解