zoukankan      html  css  js  c++  java
  • jQuery-1.9.1源码分析系列(十一) DOM操作续——克隆节点

      什么情况下使用到克隆节点?

      我们知道在对DOM操作过程中如果直接使用节点会出现节点随操作而变动的情况。比如对节点使用.after/.before/.append等方法后,节点被添加到新的地方,原来的位置上的节点被移除了。有的时候需要保留原来位置上的节点,仅仅是需要一个副本添加到对应位置,这个时候克隆就有了使用场景。

      jQuery.fn.clone克隆当前匹配元素集合的一个副本,并以jQuery对象的形式返回。

      你还可以指定是否复制这些匹配元素(甚至它们的子元素)的附加数据( data()函数 )和绑定事件。

      jQueyr.fn.clone: function( withDataAndEvents, deepDataAndEvents )参数描述

    withDataAndEvents

    可选/Boolean类型,是否同时复制元素的附加数据和绑定事件,默认为false。

    deepWithDataAndEvents

    可选/Boolean类型,是否同时复制元素的所有子元素的附加数据和绑定事件,默认值即为参数withDataAndEvents的值。

      

    a.克隆函数的底层实现步骤分解如下(jQuery.clone)


      第一步,先克隆出DOM节点。对支持正确的节点克隆(即支持elem.cloneNode并保证克隆无误)的DOM节点直接使用cloneNode(true),否则自建一个节点来保存被克隆数据然后获取该节点。

    if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
        clone = elem.cloneNode( true );
    
    // IE<=8 不能正确克隆已分离、未知的节点
    //直接新建一个相同的节点,然后获取
    } else {
      //fragmentDiv是全局变量 fragmentDiv.innerHTML
    = elem.outerHTML; fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); }

      第二步,如果是IE浏览器下,则需要通过fixCloneNodeIssues( node, destElements[i] );来逐个修正IE克隆问题。IE克隆解决方案全部包含在了fixCloneNodeIssues中,下一节详细分析。里面的jQuery.support内容点击这里查看更多

    //针对ie克隆问题修正
    if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
        (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
    
        //在这里我们不使用Sizzle的原因是: http://jsperf.com/getall-vs-sizzle/2
        destElements = getAll( clone );
        srcElements = getAll( elem );
    
        //修正所有IE克隆问题
        for ( i = 0; (node = srcElements[i]) != null; ++i ) {
            // Ensure that the destination node is not null; Fixes #9587
            if ( destElements[i] ) {
                fixCloneNodeIssues( node, destElements[i] );
            }
        }
    }

       第三步,如果要克隆缓存数据(包括普通数据和绑定事件),克隆之

    //克隆绑定的事件
    if ( dataAndEvents ) {
        if ( deepDataAndEvents ) {
            srcElements = srcElements || getAll( elem );
            destElements = destElements || getAll( clone );
    
            for ( i = 0; (node = srcElements[i]) != null; i++ ) {
                cloneCopyEvent( node, destElements[i] );
            }
        } else {
            cloneCopyEvent( elem, clone );
        }
    }

      备注:cloneCopyEvent函数中会将原节点的数据保存到克隆节点中,然后将原节点的事件绑定到新的克隆节点上

        function cloneCopyEvent( src, dest ) {
    
            if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
                return;
            }
    
            var type, i, l,
            oldData = jQuery._data( src ),
            curData = jQuery._data( dest, oldData ),//dest是克隆对的节点
            events = oldData.events;
    
            if ( events ) {
                //保证被克隆的节点的事件对象干净,确保没有后面添加的事件没有重复
                delete curData.handle;
                curData.events = {};
    
                for ( type in events ) {
                    for ( i = 0, l = events[ type ].length; i < l; i++ ) {
                        jQuery.event.add( dest, type, events[ type ][ i ] );
                    }
                }
            }
    
            // 使克隆的数据对象化
            if ( curData.data ) {
                curData.data = jQuery.extend( {}, curData.data );
            }
        }
    View Code

      第四步,保护script计算历史(全局性地标记scripts代码段已经被执行过了),并回收内存,返回克隆节点

    destElements = getAll( clone, "script" );
    if ( destElements.length > 0 ) {
        setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
    }
    destElements = srcElements = node = null;
    return clone;

    b.IE克隆问题汇总fixCloneNodeIssues(src,dest)


      src是原节点,dest是src的克隆节点。

      IE克隆问题列一下(IE8+)

      1.IE6-8当使用cloneNode会克隆事件(这些事件绑定通过attachEvent)。为保证统一性,需要清除克隆的事件,为后续统一克隆事件做准备

        // IE6-8当使用cloneNode复制事件(这些事件绑定通过attachEvent)时进入该分支
        //清除原来的事件,为克隆事件做准备
        if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
            data = jQuery._data( dest );
    
            for ( e in data.events ) {
                jQuery.removeEvent( dest, e, data.handle );
            }
            dest.removeAttribute( jQuery.expando );
        }

      2.IE8-克隆脚本标签script的时候克隆的内容结果会是空白。我们需要给他重新赋值,并确保他不会执行脚本内容。

        //IE克隆脚本时内容为空白,并试图执行新设置的文本
        if ( nodeName === "script" && dest.text !== src.text ) {
            disableScript( dest ).text = src.text;
            restoreScript( dest );
            }

      3.IE6-10不能克隆使用的classid获取的对象元素的子节点。IE10下,如果父节点为null,则会抛出NoModificationAllowedError异常。需要使用原节点的outerHTML和innerHTML重新赋值。

        //IE6-10不能克隆使用的classid获取的对象元素的子节点。
        //IE10下,如果父节点为null,则会抛出NoModificationAllowedError异常
        else if ( nodeName === "object" ) {
            if ( dest.parentNode ) {
                dest.outerHTML = src.outerHTML;
            }
    
            //对于IE9,这个条分支不可避免。
            //IE9中克隆对象元素,上述outerHTML策略是不充分的。
            //如果src具有的innerHTML并且克隆节点却没有,
            //复制src.innerHTML到dest.innerHTML #10324
            if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
                dest.innerHTML = src.innerHTML;
            }
    
        }

      4.IE6-8无法克隆一个复选框或单选按钮的选中状态。需要主动设置。

        // manipulation_rcheckableType = /^(?:checkbox|radio)$/i
        else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
            //IE6-8无法坚持一个克隆的复选框或单选按钮的选中状态
            //更糟的是,如果defaultChecked值没有设置,则IE6-7无法给克隆元素选中状态的外观
            dest.defaultChecked = dest.checked = src.checked;
    
            ...
        }

      5.当克隆select标签时,IE6-8无法正确返回select默认选中状态。需要主动设置。

       //当克隆选项时,IE6-8无法正确返回select默认选中状态
         else if ( nodeName === "option" ) {
            dest.defaultSelected = dest.selected = src.defaultSelected;
         }

      6.当克隆其他类型的input和textare标签时,IE6-8不能正确设置defaultValue为正确的值。需要主动设置。

        //当克隆其他类型的input标签时,IE6-8不能正确设置defaultValue为正确的值
        else if ( nodeName === "input" || nodeName === "textarea" ) {
            dest.defaultValue = src.defaultValue;
        }

      

      里面用到disableScript这个函数。函数目的是改变script的type,从而保证在给script赋值后不会被作为脚本执行。这个方式我们可以借鉴

    //为安全DOM操作替换/保存script节点元素type属性
    function disableScript( elem ) {
        var attr = elem.getAttributeNode("type");
        elem.type = ( attr && attr.specified ) + "/" + elem.type;
        return elem;
    }
  • 相关阅读:
    静下来好好学习
    深入学习Redis主从复制
    深入学习Redis持久化
    TPS、并发用户数、吞吐量关系
    P1067 多项式输出
    P1014 Cantor表
    P2089 烤鸡
    P1579 哥德巴赫猜想(升级版)
    P1217 [USACO1.5]回文质数 Prime Palindromes
    校内比赛 城市交通费
  • 原文地址:https://www.cnblogs.com/chuaWeb/p/jQuery-1-9-1-DOM-Manip2.html
Copyright © 2011-2022 走看看