jQuery 源码分析(十九) DOM遍历模块详解
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对象,这里理解起来有点难度,需要多理一下代码。
jQuery 源码分析(十九) DOM遍历模块详解的更多相关文章
- jQuery 源码分析(二十一) DOM操作模块 删除元素 详解
本节说一下DOM操作模块里的删除元素模块,该模块用于删除DOM里的某个节点,也可以理解为将该节点从DOM树中卸载掉,如果该节点有绑定事件,我们可以选择保留或删除这些事件,删除元素的接口有如下三个: e ...
- jQuery 源码分析(十二) 数据操作模块 html特性 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第1个部分:HTML特性部分,html特性部分是对原生方法getAttribute()和setAttribute()的封装,用于修改DOM元素的特性 ...
- jQuery 源码分析(十四) 数据操作模块 类样式操作 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第3个部分:类样式操作部分,用于修改DOM元素的class特性的,对于类样式操作来说,jQuery并没有定义静态方法,而只定义了实例方法,如下: a ...
- jQuery 源码分析(十五) 数据操作模块 val详解
jQuery的属性操作模块总共有4个部分,本篇说一下最后一个部分:val值的操作,也是属性操作里最简单的吧,只有一个API,如下: val(vlaue) ;获取匹配元素集合中第一个元素的 ...
- Vue.js 源码分析(十二) 基础篇 组件详解
组件是可复用的Vue实例,一个组件本质上是一个拥有预定义选项的一个Vue实例,组件和组件之间通过一些属性进行联系. 组件有两种注册方式,分别是全局注册和局部注册,前者通过Vue.component() ...
- Vue.js 源码分析(十) 基础篇 ref属性详解
ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...
- jQuery 源码分析(十六) 事件系统模块 底层方法 详解
jQuery事件系统并没有将事件监听函数直接绑定到DOM元素上,而是基于数据缓存模块来管理监听函数的,事件模块代码有点多,我把它分为了三个部分:分底层方法.实例方法和便捷方法.ready事件来讲,好理 ...
- jQuery 源码分析(十八) ready事件详解
ready事件是当DOM文档树加载完成后执行一个函数(不包含图片,css等),因此它的触发要早于load事件.用法: $(document).ready(fun) ;fun是一个函数,这样当DOM树加 ...
- jQuery 源码分析(十) 数据缓存模块 data详解
jQuery的数据缓存模块以一种安全的方式为DOM元素附加任意类型的数据,避免了在JavaScript对象和DOM元素之间出现循环引用,以及由此而导致的内存泄漏. 数据缓存模块为DOM元素和JavaS ...
随机推荐
- [洛谷P1169][题解][ZJOI2007]午餐
这是题目吗? 显然的DP,讲几个重要的地方 1.贪心:让吃饭时间长的先排队(证明从略) 2.状态: f[i][j][k]代表前i个人,一号时间j,二号时间k显然MLE 所以压缩成f[i][j]代表前i ...
- vuetify,vux,Mint UI 等框架的选择
vuetify: https://vuetifyjs.com/zh-Hans/getting-started/quick-start NutUI:https://github.com/jdf2e/nu ...
- 【RTOS】基于V7开发板的uCOS-III,uCOS-II,RTX4,RTX5,FreeRTOS原版和带CMSIS-RTOS V2封装层版全部集齐
RTOS模板制作好后,后面堆各种中间件就方便了. 1.基于V7开发板的最新版uCOS-II V2.92.16程序模板,含MDK和IAR,支持uC/Probe https://www.cnblogs.c ...
- [python / selenium] - 用python刷公选课是一种什么体验?
前言 看公选课还是能学到很多知识的,这里是给大家提供一个selenium的使用思路(好好学公选课,我真的看了) 思路 当观看者移动鼠标到某一范围时就会停止播放,就让selenium一直将鼠标悬停在视频 ...
- Python Weekly 419
文章,教程或讲座 如何用 Dropbox Security 构建用于日志系统的威胁检测和事件响应的工具 https://blogs.dropbox.com/tech/2019/10/how-dropb ...
- ETCD:运行时重新配置
原文地址:runtime reconfiguration etcd带有增量运行时重新配置的支持.允许我们在集群运行的时候更新集群成员关系. 仅当大多数集群成员都在运行时,才能处理重新配置请求,强烈建议 ...
- CAD编辑器哪个好用?如何使用CAD编辑器
说起CAD图纸很多的朋友都很熟悉,因为CAD图纸在很多领域都有广泛的应用.那CAD图纸都是使用CAD编辑器制图软件来进行绘制的,图纸的格式都是为dxf格式或者是dwg格式的.对于才接触CAD的伙伴们来 ...
- vue jsx与render的区别及基本使用
vue template语法简单明了,数据操作与视图分离,开发体验友好.但是在某些特定场合中,会限制一些功能的扩展,如动态使用过滤器.解析字符串类型的模板文件等.以上功能的实现可以借助vue的rend ...
- Azkaban(3.x)编译安装使用
官网地址:https://azkaban.readthedocs.io Azkaban 有三种部署方式:单服务模式.2个服务模式.分布式多服务模式 简单实用仅需单服务模式即可 2个服务模式,需要配置m ...
- Mysql存储过程--大于十分钟执行
--存储过程 DELIMITER | DROP PROCEDURE IF EXISTS update_tatus | CREATE PROCEDURE update_status() BEGIN mi ...