本節說一下DOM操作模塊里的複製元素子模塊,該模塊可以複製一個DOM節點,並且可選擇的設置是否複製其數據緩存對象(包含事件信息)和是否深度複製(子孫節點等),API如下: $.clone(elem, dataAndEvents, deepDataAndEvents) ;jQuery底層方法,返回DO ...
本節說一下DOM操作模塊里的複製元素子模塊,該模塊可以複製一個DOM節點,並且可選擇的設置是否複製其數據緩存對象(包含事件信息)和是否深度複製(子孫節點等),API如下:
- $.clone(elem, dataAndEvents, deepDataAndEvents) ;jQuery底層方法,返回DOM引用 ;elem是要複製的DOM元素,dataAndEvents和deepDataAndEvents分別表示是否複製克隆元素的數據和事件 和 是否複製深度複製數據和事件
- $.fn.clone(dataAndEvents,deepDataAndEvents) ;jQuery實例方法,返回jQuery對象 ;參數同上,如果指定了參數1,參數2為空時,則參數2等於參數1
writer by:大沙漠 QQ:22969969
還是先舉個慄子:
<!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 style="width: 150px;height: 50px;background: #cfc;"> <p>今天天氣很好</p> </div> <button id="b1">按鈕1</button> <button id="b2">按鈕2</button> <button id="b3">按鈕3</button> <button id="b4">按鈕4</button> <script> $('div').click(function(){console.log('div click');}) //給div綁定一個click事件 $('p').click(function(){console.log('p click');}) //給p綁定一個click事件 /*點擊按鈕1、按鈕2、按鈕3、按鈕4都將會複製div,並添加到body的末尾,再點擊複製出來的區塊里的p元素時輸出如下:*/ $('#b1').click(function(){$('div').clone().appendTo('body');}) //沒有任何輸出 $('#b2').click(function(){$('div').clone(true,false).appendTo('body');}) //輸出:clone_d1 click $('#b3').click(function(){$('div').clone(true,true).appendTo('body');}) //輸出:clone_p1 click、clone_d1 click $('#b4').click(function(){$('div').clone(true).appendTo('body');}) //輸出:clone_p1 click、clone_d1 click ;因為參數2省略,預設等於參數1 </script> </body> </html>
渲染如下:
我們在div和p上分別綁定了兩個事件,點擊p元素(今天天氣很好這個文本)控制台輸出如下:
另外我們點擊任意一個按鈕都會克隆div元素,渲染如下:
只不過點擊克隆出來的元素的p元素,也就是箭頭點擊的文字,控制台輸出的內容不同,對於四個按鈕分別如下:
- 按鈕1 ;無輸出 ;clone()未傳入參數,因此不會複製數據緩存對象
- 按鈕2 ;輸出:一行:div click ;clone()傳入了true和false,因此只會對div的數據緩存對象做一次拷貝,對於p就不會拷貝了
- 按鈕3 ;輸出兩行:p click和div click ;clone()傳入了兩個true,會進行深層次的拷貝,子孫節點的數據對象都會拷貝過來
- 按鈕4 ;輸出兩行:p click和div click ;clone()只傳入一個true,這和按鈕3是一樣的,且看下麵代碼分析
源碼分析
先介紹一下$.clone(),也就是jQuery的底層方法,如下:
jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { //複製DOM元素,並修正不相容屬性。dataAndEvents:是否複製數據和事件。deepDataAndEvents:是否複製後代元素的數據和事件。 var srcElements, destElements, i, // IE<=8 does not properly clone detached, unknown element nodes clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? //如果瀏覽器支持HTML5元素,或者不支持html5且原始元素不含有html5元素 elem.cloneNode( true ) : //調用原生方法.clone(deep)複製DOM元素 shimCloneNode( elem ); //否則調用函數shimCloneNode()通過"安全文檔片段"複製HTML5元素 if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && //修正不相容性 (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { // IE copies events bound via attachEvent when using cloneNode. // Calling detachEvent on the clone will also remove the events // from the original. In order to get around this, we use some // proprietary methods to clear the events. Thanks to MooTools // guys for this hotness. cloneFixAttributes( elem, clone ); // Using Sizzle here is crazy slow, so we use getElementsByTagName instead srcElements = getAll( elem ); destElements = getAll( clone ); // Weird iteration because IE will replace the length property // with an element if you are cloning the body and one of the // elements on the page has a name or id of "length" for ( i = 0; srcElements[i]; ++i ) { // Ensure that the destination node is not null; Fixes #9587 if ( destElements[i] ) { cloneFixAttributes( srcElements[i], destElements[i] ); } } } // Copy the events from the original to the clone if ( dataAndEvents ) { //如果複製數據和事件 cloneCopyEvent( elem, clone ); //調用cloneCopyEvent()數據 if ( deepDataAndEvents ) { //如果是深度複製 srcElements = getAll( elem ); //獲取源DOM節點所有子節點的DOM引用 destElements = getAll( clone ); //獲取目標DOM節點所有子節點的DOM引用 for ( i = 0; srcElements[i]; ++i ) { //遍歷源DOM節點的子節點 cloneCopyEvent( srcElements[i], destElements[i] ); //逐個複製數據緩存 } } } srcElements = destElements = null; // Return the cloned set return clone; //最後返回clone,也就是複製出來的DOM元素 }, })
$.clone()調用原生的cloneNode()方法拷貝了一份DOM,對於數據緩存則用cloneCopyEvent()進行拷貝,該函數實現如下:
function cloneCopyEvent( src, dest ) { //複製數據和事件 ;$.clone()函數調用 ;src是源DOM節點,dest是目標節點 if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { //如果dest不是元素節點或 src不含有數據,則直接返回 return; } var type, i, l, oldData = jQuery._data( src ), //獲取原對象內部數據 curData = jQuery._data( dest, oldData ), //設置目標對象的內部數據 ;這時src和dest兩個對象內的事件對象(events和handle屬性)都是指向同一個的 events = oldData.events; //原對象的事件對象 if ( events ) { //如果源DOM對象有綁定事件處理函數,則刪除目標DOM對象的事件信息,再重新綁定 delete curData.handle; //刪除目標對象的監聽句柄 curData.events = {}; //重置目標對象的事件列表 for ( type in events ) { //遍歷源DOM對象的事件列表 for ( i = 0, l = events[ type ].length; i < l; i++ ) { //遍歷綁定的每個函數 jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); //依次在目標DOM對象上進行綁定事件操作 } } } // make the cloned public data object a copy from the original if ( curData.data ) { //如果源DOM對象含有自定義事件對象 curData.data = jQuery.extend( {}, curData.data ); //也單獨拷貝一份,再保存到curData.data中 } }
在源碼里可以看到,如果有綁定了事件對象則會調用jQuery.event.add()依次進行綁定,實現就是這樣子的。
對於實例方法$.fn.clone()來說,它的實現如下:
jQuery.fn.extend({ clone: function( dataAndEvents, deepDataAndEvents ) { //創建匹配元素集合的深度複製副本。dataAndEvents:可選的布爾值,表示是否複製數據和事件,預設為false。deepDataAndEvents:可選的布爾值,表示是否深度複製數據和事件,預設為false。 dataAndEvents = dataAndEvents == null ? false : dataAndEvents; //修正dataAndEvents參數 deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; //修正deepDataAndEvents參數 return this.map( function () { //調用發那個發.map()遍歷匹配元素集合,在回調函數函數中調用jQuery.clone()複製每個匹配元素。 return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); //調用底層的jQuery.clone()方法 }); }, })
這裡可以看到,對於$.fn.clone()來說,如果參數2沒有傳遞,則會修正未參數1,上面例子的按鈕4就是執行到這裡的。