jQuery源码解析之jQueryeventdispatch

71次阅读

共计 8449 个字符,预计需要花费 22 分钟才能阅读完成。

一、起源
jQuery.event.add() 方法最终是用 addEventListener 绑定事件的:

elem.addEventListener(type, eventHandle)

eventHandle 方法正是等于jQuery.event.dispatch()

if (!( eventHandle = elemData.handle) ) {eventHandle = elemData.handle = function( e) {
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
jQuery.event.dispatch.apply(elem, arguments) : undefined;
};
}

二、$.event.dispatch()
作用:
触发绑定的事件的处理程序

源码:

// 源码 5472 行
//nativeEvent 即原生 MouseEvent
// 触发事件的处理程序
dispatch: function(nativeEvent) {
// 修正 event 对象
// Make a writable jQuery.Event from the native event object
var event = jQuery.event.fix(nativeEvent);
console.log(event,'event5479')
var i, j, ret, matched, handleObj, handlerQueue,
args = new Array(arguments.length),
// 获取 click 事件的处理程序集合,结构如下://[// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
// delegateCount:0,
//]
// 从数据缓存中获取事件处理集合
handlers = (dataPriv.get( this, "events") || {})[event.type] || [],
//click:{// trigger:{},
// _default:{}
//}
special = jQuery.event.special[event.type] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
args[0] = event;
for (i = 1; i < arguments.length; i++) {args[ i] = arguments[i];
}
//this 即目标元素
//delegateTarget: 委托目标
event.delegateTarget = this;
// 这段代码压根不会执行,因为全局搜索没找到 preDispatch
// Call the preDispatch hook for the mapped type, and let it bail if desired
if (special.preDispatch && special.preDispatch.call( this, event) === false ) {return;}
// Determine handlers
// 结构如下
//[{
// elem:xx,
// handlers:[// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
// ]
//}]
// 获取 handler 队列
handlerQueue = jQuery.event.handlers.call(this, event, handlers);
// Run delegates first; they may want to stop propagation beneath us
i = 0;
// 没有执行 stopPropagation()的话
console.log(handlerQueue,'handlerQueue5525')
// 先判断有没有冒泡
// 再判断有没有阻止剩下的 handler 执行
while (( matched = handlerQueue[ i++] ) && !event.isPropagationStopped()) {console.log(matched,'matched5542')
event.currentTarget = matched.elem;
j = 0;
//handleObj 即单个事件处理程序
// 没有执行 stopImmediatePropagation()的话
// 依次执行每一个 handler
while (( handleObj = matched.handlers[ j++] ) &&
!event.isImmediatePropagationStopped()) {// Triggered event must either 1) have no namespace, or 2) have namespace(s)
// a subset or equal to those in the bound event (both can have no namespace).
if (!event.rnamespace || event.rnamespace.test( handleObj.namespace) ) {
// 通过循环将为 event 添加 handleObj 和 handleObj.data
event.handleObj = handleObj;
event.data = handleObj.data;
// 关键代码,执行事件处理程序 handler
ret = (( jQuery.event.special[ handleObj.origType] || {}).handle ||
handleObj.handler ).apply(matched.elem, args);
if (ret !== undefined) {
//event.result 赋值 ret
if (( event.result = ret) === false ) {
// 阻止默认行为
event.preventDefault();
// 阻止冒泡
event.stopPropagation();}
}
}
}
}
// Call the postDispatch hook for the mapped type
if (special.postDispatch) {special.postDispatch.call( this, event);
}
console.log(handlers,'event5587')
//undefined
return event.result;
},

解析:

(1)jQuery.event.fix()
作用:
将原生事件对象 MouseEvent 修正(fix)成 jQueryevent对象

源码:

// 源码 5700 行
fix: function(originalEvent) {
// 如果存在属性 id 则原样返回(因为已处理成 jQueryEvent)return originalEvent[jQuery.expando] ?
originalEvent :
new jQuery.Event(originalEvent);
},

解析:
可以看到 fix 的本质是新建一个 event 对象,再看 jQuery.Event() 方法

(2)jQuery.Event()
源码:

//click,false
// 修正 event 对象
// 源码 5777 行
//src 即 MouseEvent
jQuery.Event = function(src, props) {
// Allow instantiation without the 'new' keyword
if (!( this instanceof jQuery.Event) ) {return new jQuery.Event( src, props);
}
// Event object
//src.type=click
if (src && src.type) {
//MouseEvent
this.originalEvent = src;
//click
this.type = src.type;
// Events bubbling up the document may have been marked as prevented
// by a handler lower down the tree; reflect the correct value.
this.isDefaultPrevented = src.defaultPrevented ||
src.defaultPrevented === undefined &&
// Support: Android <=2.3 only
src.returnValue === false ?
returnTrue :
returnFalse;
// Create target properties
// Support: Safari <=6 - 7 only
// Target should not be a text node (#504, #13143)
this.target = (src.target && src.target.nodeType === 3) ?
src.target.parentNode :
src.target;
this.currentTarget = src.currentTarget;
this.relatedTarget = src.relatedTarget;
// Event type
} else {
//click
this.type = src;
}
// Put explicitly provided properties onto the event object
//false
if (props) {jQuery.extend( this, props);
}
// Create a timestamp if incoming event doesn't have one
this.timeStamp = src && src.timeStamp || Date.now();
// Mark it as fixed
// 修正的标志
this[jQuery.expando] = true;
};

解析:
简单来说,就是把原生 event 事件上的常用属性赋值到了 jQueryevent

$("#A").on("click" ,function (event) {// 这个就是 jQuery.Event()构建出的 event
console.log(event,"A 被点击了")
})

jQueryevent 结构如下:

//click 的 event 就是 jQuery.Event
jQuery.Event{
handleObj{
data:undefined,
guid: 2,
handler:function(){console.log("A 被点击了")},namespace: "clickA",
origType: "click",
selector: "#B",
type: "click.clickA",
},
originalEvent:{// 就是 MouseEvent},
target:div#B,
type: "click",
delegateTarget: div#A,
//fix 的标志
jQuery331087940272164138: true,
currentTarget: div#A,
isDefaultPrevented:xxx,
timeStamp:Date.now(),
isDefaultPrevented:function(){return false}
}

注意下 originalEvent jQuery.extend(this, props)
前者就是原生 MouseEvent,只是将原生 event 作为 jQuery.event 的 originalEvent 属性了;
后者是扩展属性,如果开发者想额外加入自定义属性的话。

(3)dataPriv.get(this, "events")
注意:
jQuery 的数据缓存里的 events 和上面说的 event 是不同的

数据缓存的 events 是用来结构如下:

{
click:[
{
type: "click",
origType: "click",
data: undefined,
handler: function(){console.log("B 委托 A 绑定 click 事件")},
guid: 1,
namespace: "",
needsContext: undefined,
selector: #B,
},
{
type: "click",
origType: "click",
data: undefined,
handler: function(){console.log("A 绑定 click 事件")},
guid: 2,
namespace: "",
needsContext: undefined,
selector: undefined,
},
// 事件委托的数量
delegateCount:1,
],
focus:[
{
type: "focus",
origType: "focus",
data: undefined,
handler: function(){console.log("A 绑定 focus 事件")},
guid: 3,
namespace: "",
needsContext: undefined,
selector: undefined,
},
delegateCount:0,
],
}

(4) jQuery.event.handlers
作用:
获取 handler 队列

源码:

jQuery.event = {
// 源码 5547 行
// 组装事件处理队列
//event 是 fix 过的 MouseEvent, handlers
handlers: function(event, handlers) {
var i, handleObj, sel, matchedHandlers, matchedSelectors,
handlerQueue = [],
//0
delegateCount = handlers.delegateCount,
// 目标元素
cur = event.target;
//handlers,第一个 handler 是委托事件,第二个 handler 是自身事件
// Find delegate handlers
if ( delegateCount &&
// Support: IE <=9
// Black-hole SVG <use> instance trees (trac-13180)
cur.nodeType &&
// Support: Firefox <=42
// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
// Support: IE 11 only
// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
!(event.type === "click" && event.button >= 1) ) {
// 循环,event.target 冒泡到 cur.parentNode,// 直至绑定的目标元素 #A,退出循环
for (; cur !== this; cur = cur.parentNode || this) {console.log(cur,'cur5618')
// Don't check non-elements (#13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
if (cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true) ) {matchedHandlers = [];
matchedSelectors = {};
// 在每一层,依次将委托的事件 push 进 matchedHandlers
// 顺序由下到上
for (i = 0; i < delegateCount; i++) {handleObj = handlers[ i];
//sel 就是 #C
// Don't conflict with Object.prototype properties (#13203)
sel = handleObj.selector + " ";
if (matchedSelectors[ sel] === undefined ) {matchedSelectors[ sel] = handleObj.needsContext ?
jQuery(sel, this).index(cur) > -1 :
// 注意:jQuery.find()和 jQuery().find()是不一样的
jQuery.find(sel, this, null, [ cur] ).length;
}
if (matchedSelectors[ sel] ) {matchedHandlers.push( handleObj);
}
}
// 然后将该层委托事件的数组放进 handlers 中
//handlerQueue 是所有层委托事件的集合
if (matchedHandlers.length) {handlerQueue.push( { elem: cur, handlers: matchedHandlers} );
}
}
}
}
// Add the remaining (directly-bound) handlers
// 最终冒泡到 this 元素
cur = this;
//1<2
// 将除委托事件的事件(如自身绑定的事件)放入 handlerQueue 中
if (delegateCount < handlers.length) {handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount) } );
}
//[{
// elem:xx,
// handlers:[// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
// ]
//}]
return handlerQueue;
},
}

解析:
注意下这个双层循环,目的是把每一层的委托事件的集合 pushmatchedHandlers,然后再将 matchedHandlers 放进 handlerQueue 队列

在处理完每层的委托事件后,将剩下的自身绑定事件再 pushhandlerQueue队列中

也就是说,handlerQueue的结构如下:

[
// 委托事件
{
elem:xx,
handlers:[{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
]
},
// 自身绑定事件
{
elem:xxx,
handlers:[{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 3},
{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 4},
]
},
]

(5)回过头再往下看 dispatch 源码,是两个 while 循环,举个例子来说明下:

<div id="A" style="background-color: deeppink">
这是 A
<div id="B" style="background-color: bisque">
这是 B
</div>
</div>
$("#A").on("click" ,function (event) {console.log(event,"A 被点击了")
})
$("#A").on("click" ,"#B",function (event) {console.log(event,"点击了 B,即 B 委托 A 的 click 事件被点击了")
})

那么会
先循环并 执行 委托事件,
handler=function (event) {console.log(event,"点击了 B,即 B 委托 A 的 click 事件被点击了")}
再循环并 执行 目标元素自身绑定事件,
handler=function (event) {console.log(event,"A 被点击了")}
前提是 冒泡不被阻止

最后,执行 click 事件的事件处理程序的关键代码如下:

handleObj.handler.apply(matched.elem, args)

(完)

正文完
 0