jQuery源码解析之clone()
前言:这篇讲完后,jQuery的文档处理
就告一段落了,有空我把这部分整合下,发一篇文章目录。
一、示例代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>jQuery源码解析之clone()</title> </head> <body> <script src="jQuery.js"></script> <div id="divTwo"> <p id="pTwo">这是divTwo <span id="spanTwo">这是spanTwo</span> </p> </div> <div id="divOne"> </div> <script> $("#pTwo").click(function () { alert("pTwo被点击了") }) $("#spanTwo").click(function (e) { //阻止冒泡 e.stopPropagation() alert("spanTwo被点击了") }) // $("#pTwo").clone().appendTo($("#divOne")) // $("#pTwo").clone(true,false).appendTo($("#divOne")) $("#pTwo").clone(true,true).appendTo($("#divOne")) </script> </body> </html>
二、$()
.clone()
作用:
生成被选元素的副本,包含子节点、文本和属性
注意:$('div')
.clone(true
) 表示克隆目标节点的事件和数据
$('div')
.clone(true,true
) 表示克隆目标节点及其子节点的事件和数据
源码:
jQuery.fn.extend({ //克隆目标节点及其子节点 //dataAndEvents是否克隆目标节点的事件和数据,默认是false //deepDataAndEvents是否克隆目标节点子节点的事件和数据,默认值是dataAndEvents //源码6327行 clone: function( dataAndEvents, deepDataAndEvents ) { //默认是false dataAndEvents = dataAndEvents == null ? false : dataAndEvents; //默认是dataAndEvents deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; //循环调用jQuery.clone return this.map( function() { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); } ); }, });
解析:
可以看到,这里还是比较简单的,需要注意的就是参数deepDataAndEvents
不填的话,其值是根据参数dataAndEvents
的值来定的
三、jQuery.clone()
作用同上
源码:
jQuery.extend( { //源码6117行 //生成被选元素的副本,包含子节点、文本和属性 clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, //拷贝目标节点的属性和值 //如果为true,则包括拷贝子节点的所有属性和值 clone = elem.cloneNode( true ), //判断elem是否脱离文档流 inPage = jQuery.contains( elem.ownerDocument, elem ); // Fix IE cloning issues //兼容性处理,解决IE bug if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); srcElements = getAll( elem ); for ( i = 0, l = srcElements.length; i < l; i++ ) { fixInput( srcElements[ i ], destElements[ i ] ); } } // Copy the events from the original to the clone //从目标节点克隆事件,并绑定给克隆的元素 if ( dataAndEvents ) { //克隆子节点的事件和数据 if ( deepDataAndEvents ) { //源节点 srcElements = srcElements || getAll( elem ); //克隆节点 destElements = destElements || getAll( clone ); for ( i = 0, l = srcElements.length; i < l; i++ ) { cloneCopyEvent( srcElements[ i ], destElements[ i ] ); } } //只克隆目标节点和数据 else { cloneCopyEvent( elem, clone ); } } //将script标签设为已运行 // Preserve script evaluation history destElements = getAll( clone, "script" ); if ( destElements.length > 0 ) { setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); } // Return the cloned set return clone; }, })
解析:
可以看到这部分源码主要分为三大块:
(1)解决 IE 的 bug,主要是在fixInput()
方法上进行处理
(2)从目标节点克隆数据、添加事件给克隆的元素
(3)将克隆的元素中的script标签设为已运行
四、fixInput()
作用:
(1)解决 IE 无法保存克隆的单选、多选的状态的 bug
(2)解决 IE 无法将克隆的选项返回至默认选项状态的 bug
源码:
//解决IE的bug:(1)无法保存克隆的单选、多选的状态 (2)无法将克隆的选项返回至默认选项状态 // Fix IE bugs, see support tests //源码5937行 function fixInput( src, dest ) { var nodeName = dest.nodeName.toLowerCase(); // Fails to persist the checked state of a cloned checkbox or radio button. //IE无法保存克隆的单选框和多选框的选择状态 if ( nodeName === "input" && rcheckableType.test( src.type ) ) { dest.checked = src.checked; } //IE无法将克隆的选项返回至默认选项状态 // Fails to return the selected option to the default selected state when cloning options else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } }
解析:
本质就是将目标元素的checked属性
和defaultValue属性
手动赋值给克隆的元素。
五、cloneCopyEvent()
作用:$().clone()
的关键方法,用来从目标节点克隆数据、添加事件给克隆的元素
注意:
jQuery 采用数据分离的方法来保存 DOM 上的事件和数据,利用 uuid 标记每个 DOM 元素,然后在内存上,将每个 DOM 元素相关的数据放到内存中,然后在 uuid 和内存的数据之间建立映射。
优点是方便复制数据。
注意:事件是不可赋值的,只能一个个添加!
示意图:
源码:
//src:目标元素 //dest:克隆的元素 //源码5902行 function cloneCopyEvent( src, dest ) { var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; } //拷贝jQuery内部数据:事件、处理程序等 // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { //private data old,即目标元素的数据 //注意:jQuery是通过uuid将目标元素进行标记, //然后将与目标元素相关的数据都放到内存中 //通过uuid和内存的数据建立映射 //这种数据分离的做法有利于复制数据,但不能复制事件 pdataOld = dataPriv.access( src ); //private data current,即为克隆的元素设置数据 pdataCur = dataPriv.set( dest, pdataOld ); events = pdataOld.events; //如果事件存在 if ( events ) { //移除克隆对的元素的处理程序和事件 delete pdataCur.handle; pdataCur.events = {}; //依次为克隆的元素添加事件 //注意:事件是不能被复制的,所以需要重新绑定 for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type, events[ type ][ i ] ); } } } } // 2. Copy user data //拷贝用户数据 if ( dataUser.hasData( src ) ) { udataOld = dataUser.access( src ); udataCur = jQuery.extend( {}, udataOld ); //为克隆的元素设置数据 dataUser.set( dest, udataCur ); } }
解析:
(1)拷贝 jQuery 内部数据(事件、处理程序)
拷贝赋值数据:
pdataOld = dataPriv.access( src ); //private data current,即为克隆的元素设置数据 pdataCur = dataPriv.set( dest, pdataOld );
拷贝添加事件:
jQuery.event.add( dest, type, events[ type ][ i ] );
(2)拷贝用户数据
dataUser.set( dest, udataCur );
六、setGlobalEval()
作用:
设置目标元素内部的<script>
标签为已执行
源码:
//设置目标元素内部的`<script>`标签为已执行 //源码4934行 // Mark scripts as having already been evaluated function setGlobalEval( elems, refElements ) { var i = 0, l = elems.length; for ( ; i < l; i++ ) { dataPriv.set( elems[ i ], "globalEval", !refElements || dataPriv.get( refElements[ i ], "globalEval" ) ); } }
Github:
https://github.com/AttackXiaoJinJin/jQueryExplain
(完)