前言:這篇講完後,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:
github.com/AttackXiaoJ…
(完)