jQuery 源码分析(二十一) DOM操作模块 删除元素 详解
本节说一下dom操作模块里的删除元素模块,该模块用于删除dom里的某个节点,也可以理解为将该节点从dom树中卸载掉,如果该节点有绑定事件,我们可以选择保留或删除这些事件,删除元素的接口有如下三个:
- empty() ;移除匹配元素的所有子元素。 ;先移除所有后代元素关联的数据和事件,以避免内存泄漏。然后移除子元素。
- remove(selector,keepdata) ;从匹配元素中移除selector元素。 ;selector是可选的选择器表达式,如果未传入则全部移除,否则只移除对应的匹配元素。keepdata可选,表示是否保留匹配元素以及它的后代元素所关联的数据和事件
- detach(selector) ;从文档中移除匹配元素集合 ;但会保留后代元素和匹配元素关联的数据和事件,常用于移除的元素稍后再次插入文档的场景。
说直接一点,
- empty()会直接移除当前匹配的元素
- remove()会将当前匹配的元素中在匹配参数1selector里的元素,如果参数2keepdata为true则保存该dom节点的事件,否则移除事件
- detach()是调用remove()实现的,用于移除当前匹配的元素,但是会保留数据 ,它是调用remove(selector,true)来实现的
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.7.1/jquery.min.js"></script> </head> <body> <div> <h1>你好</h1> </div> <p>朋友</p> <button id="b1">测试1</button> <button id="b2">测试2</button> <button id="b3">测试3</button> <script> $('h1').click(function(){console.log('h1 click')}); //给h1元素增加一个点击事件 $('p').click(function(){console.log('p click')}); //给p元素增加一个点击事件 //以下给三个按钮都会实现将h1、p元素从dom树中移除,再插入body的子节点的前面,不同的地方是点击子节点时的输出信息不同 b1.onclick=function(){$('h1,p').remove().prependto('body')} //点击测试1按钮后 h1和p元素都移动到了body子节点的最前面,但是再点击h1和p元素都没有反应 b2.onclick=function(){$('h1,p').remove('p').prependto('body')} //点击测试1按钮后 h1和p元素都移动到了body子节点的最前面,点击h1有输出,点击p没有输出 b3.onclick=function(){$('h1,p').detach().prependto('body')} //点击测试1按钮后 h1和p元素都移动到了body子节点的最前面,点击h1和p都有输出 </script> </body> </html>
渲染如下:
对应的dom树如下:
我们给h1和p元素分别绑定了一个事件,点击分别输出:h1 click和p click,,点击输出如下:
另外不管点击测试1、测试2还是测试3,都会将h1和p元素从dom树中卸载并插入到body子节点的最前面,如下:
此时再点击h1和p元素就会有不同的输出了:
- 对于测试1按钮来说 ;点击h1和p没有任何输出 ;由于调用了remove()方法且未传递参数,所以所有匹配的元素的事件都会被删除掉
- 对于测试2按钮来说 ;点击h1有输出,点击p没有输出 ;由于调用remove()方法时传递了p,所以把p元素的事件都删除了,而h1元素的事件并没有被删除
- 对于测试2按钮来说 ;点击h1和p没有任何输出 ;由于调用了detach()方法,所以所有元素的数据都被保留了,detach()内部时调用remove( selector, true )来实现的,也就是remove的参数2为true
源码分析
讲解empty()、remove()和detach()之前先介绍一个jquery的api,就是$.cleandata(elems),参数是dom节点的集合(可以是document.getelementsbytagname的返回值),它用于移除一个dom节点上绑定的jquery事件的,我们举个栗子:
<!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> <p>hello world</p> <button>测试</button> <script> $('p').click(function(){console.log('p click')}); //给p元素增加一个点击事件 $('button').click(function(){ $.cleandata(document.getelementsbytagname('p')) //给button按钮添加一个button事件,内部调用$.cleandata去清楚p元素上绑定的事件 }) </script> </body> </html>
渲染如下:
初始化时我们点击hello world控制台会输出信息,如果点击按钮后再点击hello world就不会输出了,因为$.cleandata把该dom元素的事件给卸载了,$.cleandata实现如下:
jquery.extend({ cleandata: function( elems ) { //移除多个dom元素的全部数据和事件, elems是待移除数据和事件的dom元素数组。 var data, id, cache = jquery.cache, special = jquery.event.special, deleteexpando = jquery.support.deleteexpando; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { //遍历每个匹配元素 if ( elem.nodename && jquery.nodata[elem.nodename.tolowercase()] ) { //如果不支持设置属性,则跳过本次循环。 jquery.nodata是一个数组,保存了不支持扩展属性的节点名称。 continue; } id = elem[ jquery.expando ]; //属性名,注1 if ( id ) { //如果该dom对象有jquery.expando属性,则表示设置过数据。 data = cache[ id ]; if ( data && data.events ) { //如果dom的关联数据缓存对象存在且含有events属性,说明在该dom上绑定过事件,则移除 for ( var type in data.events ) { //data.events是dom元素的事件缓存对象,存储了dom元素的所有事件 if ( special[ type ] ) { jquery.event.remove( elem, type ); // this is a shortcut to avoid jquery.event.remove's overhead } else { jquery.removeevent( elem, type, data.handle ); } } // null the dom reference to avoid ie6/7/8 leak (#7054) if ( data.handle ) { data.handle.elem = null; } } if ( deleteexpando ) { //如果jquery.support.deleteexpando;返回ture,即浏览器支持删除dom元素上的扩展属性 delete elem[ jquery.expando ]; //删除dom元素的jquery.expando属性 } else if ( elem.removeattribute ) { //如果浏览器不支持删除dom元素上的扩展属性,则调用removeattribute删除属性 elem.removeattribute( jquery.expando ); } delete cache[ id ]; //删除dom元素的数据缓存对象cache[id] } } } /*略*/ })
注1:jquery绑定的事件对应的信息都存在$.cache里,键名是对应dom对象的jquery.expando,也就是这里的elem[ jquery.expando ],有疑问可以看看之前事件的讲解:https://www.cnblogs.com/greatdesert/p/11679334.html
cleandata就是删除参数elems里所有dom元素的事件,本节的empty()和remove()都是基于cleandata实现的,empty()实现如下:
jquery.fn.extend({ empty: function() { //从文档中移除匹配元素的所有子元素。 for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { //遍历当前匹配的每个元素 // remove element nodes and prevent memory leaks if ( elem.nodetype === 1 ) { jquery.cleandata( elem.getelementsbytagname("*") ); ////先移除所有后代元素关联的数据和事件,以避免内存泄漏 } // remove any remaining nodes while ( elem.firstchild ) { //然后移除子元素。 elem.removechild( elem.firstchild ); } } return this; }, })
empty()内首先调用cleandata()移除所有子孙节点的数据和事件,然后通过一个while()循环卸载掉每个dom节点,比较好理解哈,对于remove()来说,实现如下:
jquery.fn.extend({ remove: function( selector, keepdata ) { //从匹配元素中移除selector元素,该方法会遍历匹配元素集合,先删除后代元素和匹配元素的数据和事件,以避免内存泄漏,然后移除匹配元素。 for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { //遍历当前匹配元素 if ( !selector || jquery.filter( selector, [ elem ] ).length ) { //如果没有传入selector参数,或者传入了selector参数,并且当前元素与之匹配 if ( !keepdata && elem.nodetype === 1 ) { //如果没有传入keepdata参数且elem是元素节点 jquery.cleandata( elem.getelementsbytagname("*") ); //移除后代元素的全部数据和事件 jquery.cleandata( [ elem ] ); //移除元素节点。 } if ( elem.parentnode ) { //调用原生方法removechild()移除节点。 elem.parentnode.removechild( elem ); } } } return this; }, detach: function( selector ) { return this.remove( selector, true ); //调用remove()来实现的,参数2传入了true }, })
remove()方法会根据selector来选择匹配元素,并将该匹配元素从dom树中卸载,如果有传入keepdata则保存数据,否则将数据删除,对于detach来说,就是调用remove()来实现的
推荐阅读
-
jQuery 源码解析(二十二) DOM操作模块 复制元素 详解
-
jQuery 源码分析(十三) 数据操作模块 DOM属性 详解
-
jQuery 源码解析(二十三) DOM操作模块 替换元素 详解
-
jQuery 源码分析(十二) 数据操作模块 html特性 详解
-
jQuery 源码解析(二十四) DOM操作模块 包裹元素 详解
-
jQuery 源码分析(十九) DOM遍历模块详解
-
jQuery 源码分析(二十一) DOM操作模块 删除元素 详解
-
jQuery 源码分析(十五) 数据操作模块 val详解
-
jQuery 源码分析(二十) DOM操作模块 插入元素 详解
-
jQuery 源码分析(十四) 数据操作模块 类样式操作 详解