前言:
看 jQuery 源码的一个痛点就是调用一个函数时,里面会调用 N 个其他函数,然后这 N 个函数里面又会调用 M 个其他其他函数。。
本篇文章主要是对上篇文章—[jQuery 源码解析之 detach()/empty()/remove()/unwrap()]() 中两个函数 getAll 和 cleanData() 进行解析。
一、getAll(context, tag)
作用:
用来获取 context 上的 tag 标签,或者是将 context 和 context 里的 tag 标签的元素合并
源码:
// 一般是传的 node,’script’
// 应该是用来获取 context 上的 tag 标签,或者是将 context 和 context 里的 tag 标签的元素合并
// 源码 4893 行
function getAll(context, tag) {
// Support: IE <=9 – 11 only
// Use typeof to avoid zero-argument method invocation on host objects (#15151)
var ret;
console.log(context,typeof context.getElementsByTagName,typeof context.querySelectorAll,’context4894′)
// 如果 context 存在 getElementsByTagName 的方法的话
if (typeof context.getElementsByTagName !== “undefined”) {
//tag:script
// 从 context 中获取 script 标签的节点
ret = context.getElementsByTagName(tag || “*”)
console.log(tag,ret,’ret4897′)
}
//DocumentFragment 没有 getElementsByTagName 方法,但有 querySelectorAll 方法
else if (typeof context.querySelectorAll !== “undefined”) {
ret = context.querySelectorAll(tag || “*”);
} else {
ret = [];
}
console.log(nodeName( context, tag),’nodeName4909′)
//nodeName() 判断两个参数的 nodename 是否相等
if (tag === undefined || tag && nodeName( context, tag) ) {
return jQuery.merge([ context], ret );
}
return ret;
}
注意:DocumentFragment 没有 getElementsByTagName 方法,但有 querySelectorAll 方法!
二、$.merge()
作用:
合并两个数组内容到第一个数组
源码:
// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
// 源码 461 行
// 将 second 合并到 first 后面
merge: function(first, second) {
var len = +second.length,
j = 0,
i = first.length;
// 依次将 second 的 item 添加到 first 后面
for (; j < len; j++) {
first[i++] = second[j];
}
//first 可能是类数组,所以需要更新下 length 属性
first.length = i;
return first;
},
需要注意的是最后的 first.length = i
三、cleanData()
作用:
清除元素节点上的事件和数据
源码:
// 清除 elems 上的数据和事件
// 源码 6146 行
cleanData: function(elems) {
var data, elem, type,
//beforeunload/blur/click/focus/focusin/focusout/
//load/mouseenter/mouseleave/pointerenter/pointerleave
special = jQuery.event.special,
i = 0;
for (; ( elem = elems[ i] ) !== undefined; i++ ) {
// 允许的节点类型
if (acceptData( elem) ) {
// 当有事件绑定到 elem 后,jQuery 会给 elem 一个属性 dataPriv.expando
// 该属性上面就绑定了事件和数据
if (( data = elem[ dataPriv.expando] ) ) {
// 如果 data 上有事件的话
if (data.events) {
// 逐个列举 data 上的事件,比如 click
for (type in data.events) {
// 如果 special 中有 data.events 上的事件
if (special[ type] ) {
// 调用 jQuery.event.remove 方法,移除 elem 上的 event 类型
jQuery.event.remove(elem, type);
// This is a shortcut to avoid jQuery.event.remove’s overhead
}
// 应该是自定义的事件
else {
// 本质即 elem.removeEventListener(type,handle)
jQuery.removeEvent(elem, type, data.handle);
}
}
}
// Support: Chrome <=35 – 45+
// Assign undefined instead of using delete, see Data#remove
// 最后将元素的 dataPriv.expando 属性置为 undefined
elem[dataPriv.expando] = undefined;
}
//dataUser 应该是用户绑定的事件
if (elem[ dataUser.expando] ) {
// 将元素的 dataUser.expando 属性置为 undefined
// Support: Chrome <=35 – 45+
// Assign undefined instead of using delete, see Data#remove
elem[dataUser.expando] = undefined;
}
}
}
}
解析:
① 依次判断 elems[i] 是否是元素节点 / 文档节点 / 对象
② 再判断 elem 的 dataPriv.expando 属性是否有 events 属性
③ 当 events 里有 jQuery.event.special 指定的 事件类型时,使用 jQuery.event.remove(elem,type) 移除事件和数据
④ 反之,则使用 jQuery.removeEvent(elem,type,data.handle) 移除事件和数据
⑤ 将 elem[dataPriv.expando] 置为 undefined
⑥ 将 elem[dataUser.expando] 置为 undefined
四、acceptData()
作用:
判断是否是指定的节点类型,返回 true/false
源码:
// 判断是否是指定的节点类型
// 只接受元素节点 1,文档节点 9,任意对象
// 返回 true/false
// 源码 4178 行
var acceptData = function(owner) {
// Accepts only:
// – Node
// – Node.ELEMENT_NODE
// – Node.DOCUMENT_NODE
// – Object
// – Any
return owner.nodeType === 1 || owner.nodeType === 9 || !(+owner.nodeType);
}
注意:
Object 类型的 nodeType 是 undefined
五、$.removeEvent()
作用:
移除 elem 上的自定义监听事件
源码:
// 移除 elem 上的自定义监听事件
// 源码 5599 行
//jQuery.removeEvent(elem,type,data.handle)
jQuery.removeEvent = function(elem, type, handle) {
// This “if” is needed for plain objects
if (elem.removeEventListener) {
elem.removeEventListener(type, handle);
}
}
本质即调用原生 JS 的 removeEventListener() 方法
(完)