jQuery-1.9.1源码分析系列(十一)DOM操作续——克隆节点

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

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

我们知道在对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/2destElements =getAll( clone );

srcElements=getAll( elem );//修正所有IE克隆问题for( i = 0; (node = srcElements[i]) !=null; ++i ) {//Ensure that the destination node is not null; Fixes #9587if( 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函数中会将原节点的数据保存到克隆节点中,然后将原节点的事件绑定到新的克隆节点上

functioncloneCopyEvent( src, dest ) {if( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {return;

}vartype, i, l,

oldData=jQuery._data( src ),

curData= jQuery._data( dest, oldData ),//dest是克隆对的节点events =oldData.events;if( events ) {//保证被克隆的节点的事件对象干净,确保没有后面添加的事件没有重复deletecurData.handle;

curData.events={};for( typeinevents ) {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( eindata.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异常elseif( nodeName === "object") {if( dest.parentNode ) {

dest.outerHTML=src.outerHTML;

}//对于IE9,这个条分支不可避免。//IE9中克隆对象元素,上述outerHTML策略是不充分的。//如果src具有的innerHTML并且克隆节点却没有,//复制src.innerHTML到dest.innerHTML #10324if( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {

dest.innerHTML=src.innerHTML;

}

}

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

//manipulation_rcheckableType = /^(?:checkbox|radio)$/ielseif( 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默认选中状态elseif( nodeName === "option") {

dest.defaultSelected= dest.selected =src.defaultSelected;

}

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

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

dest.defaultValue=src.defaultValue;

}

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

//为安全DOM操作替换/保存script节点元素type属性functiondisableScript( elem ) {varattr = elem.getAttributeNode("type");

elem.type= ( attr && attr.specified ) + "/" +elem.type;returnelem;

}

如果觉得本文不错,请点击右下方【推荐】!

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航