jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点
什么情况下使用到克隆节点?
我们知道在对DOM操作过程中如果直接使用节点会出现节点随操作而变动的情况。比如对节点使用.after/.before/.append等方法后,节点被添加到新的地方,原来的位置上的节点被移除了。有的时候需要保留原来位置上的节点,仅仅是需要一个副本添加到对应位置,这个时候克隆就有了使用场景。
jQuery.fn.clone克隆当前匹配元素集合的一个副本,并以jQuery对象的形式返回。
你还可以指定是否复制这些匹配元素(甚至它们的子元素)的附加数据(data()函数)和绑定事件。
jQueyr.fn.clone:function(withDataAndEvents,deepDataAndEvents)参数描述
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){ //Ensurethatthedestinationnodeisnotnull;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函数中会将原节点的数据保存到克隆节点中,然后将原节点的事件绑定到新的克隆节点上
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); } }
第四步,保护script计算历史(全局性地标记scripts代码段已经被执行过了),并回收内存,返回克隆节点。
destElements=getAll(clone,"script"); if(destElements.length>0){ setGlobalEval(destElements,!inPage&&getAll(elem,"script")); } destElements=srcElements=node=null; returnclone;
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#10324 if(jQuery.support.html5Clone&&(src.innerHTML&&!jQuery.trim(dest.innerHTML))){ dest.innerHTML=src.innerHTML; } }
4.IE6-8无法克隆一个复选框或单选按钮的选中状态。需要主动设置。
//manipulation_rcheckableType=/^(?:checkbox|radio)$/i elseif(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; }
以上内容是小编给大家介绍的关于jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点的全部叙述,希望大家喜欢。