共计 4739 个字符,预计需要花费 12 分钟才能阅读完成。
前言:这篇讲完后,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
(完)