jQuery 源码分析(二十一) DOM操做模块 删除元素 详解

本节说一下DOM操做模块里的删除元素模块,该模块用于删除DOM里的某个节点,也能够理解为将该节点从DOM树中卸载掉,若是该节点有绑定事件,咱们能够选择保留或删除这些事件,删除元素的接口有以下三个:html

  • empty()                                   ;移除匹配元素的全部子元素。        ;先移除全部后代元素关联的数据和事件,以免内存泄漏。而后移除子元素。  
  • remove(selector,keepData)    ;从匹配元素中移除selector元素。   ;selector是可选的选择器表达式,若是未传入则所有移除,不然只移除对应的匹配元素。keepData可选,表示是否保留匹配元素以及它的后代元素所关联的数据和事件
  • detach(selector)                     ;从文档中移除匹配元素集合             ;但会保留后代元素和匹配元素关联的数据和事件,经常使用于移除的元素稍后再次插入文档的场景。

说直接一点,node

  • empty()会直接移除当前匹配的元素
  • remove()会将当前匹配的元素中在匹配参数1selector里的元素,若是参数2keepData为true则保存该DOM节点的事件,不然移除事件
  • detach()是调用remove()实现的,用于移除当前匹配的元素,可是会保留数据 ,它是调用remove(selector,true)来实现的

 writer by:大沙漠 QQ:22969969jquery

举个栗子:数组

<!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元素增长一个点击事件

        //如下给三个按钮都会实现将h一、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,,点击输出以下:源码分析

另外无论点击测试一、测试2仍是测试3,都会将h1和p元素从DOM树中卸载并插入到body子节点的最前面,以下:测试

此时再点击h1和p元素就会有不一样的输出了:this

  • 对于测试1按钮来讲   ;点击h1和p没有任何输出                 ;因为调用了remove()方法且未传递参数,因此全部匹配的元素的事件都会被删除掉
  • 对于测试2按钮来讲   ;点击h1有输出,点击p没有输出      ;因为调用remove()方法时传递了p,因此把p元素的事件都删除了,而h1元素的事件并无被删除
  • 对于测试2按钮来讲   ;点击h1和p没有任何输出                ;因为调用了detach()方法,因此全部元素的数据都被保留了,detach()内部时调用remove( selector, true )来实现的,也就是remove的参数2为true

 

源码分析spa


讲解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()来实现的

相关文章
相关标签/搜索