JQuery-1.9.1 source code analysis series (10) Event System-event Commission, jquery-1.9.1 source code
JQuery event binding has several outstanding features:
1. You can bind an unlimited number of processing functions.
2. events can be delegated to the ancestor node, and do not have to be bound to the corresponding node.
3. Chain Operation
The following mainly analyzes the event Delegate design. The event source is a delegated node. The delegated node entrusts its ancestor node to perform event processing for him. This ancestor node is a delegated node.
The native event of DOM is bound to the corresponding node, and the event triggered by the corresponding node can be processed. Delegate event processing to the ancestor node. The event processing is appended to the ancestor node. What needs to be done is that the original node triggers the event. To execute the event processing that has been attached to the ancestor node, you need to ensure that the same event needs to be triggered on the ancestor node, it is also known that the original node is actually triggered.How can we achieve this?The event bubbling mechanism provides this possibility.
A. deep understanding of event bubbles
First put a self-drawn event model
DOM event flows are divided into three phases:
Phase 1: Event capture. The so-called event capture is to start from the maximum scope (document) and locate the most precise event source at the first level (the node that triggers the event, where the event source in the model is # small node). To give A simple example, I want to find Class A, Class C, and Class D in B. Then I need to find school A, Grade B, and class C, finally, I can find D exactly.
Phase 2: event triggering. Find the event source, and then execute the Bound event (if any ). For example, tell D that it's time for lunch at (this is like an event type). Then D knows that it's time for lunch, then go to lunch (execute the corresponding event ).
Stage 3: Event bubbling. After the event source completes event processing. This type of event will be passed to the ancestor node until the document (including the document) (of course, the subsequent analysis is based on the premise that the event bubble is not blocked ). That is to say, Each ancestor node of the event source triggers similar events. In the above example, D went to dinner, and class C knew it was (event type), and then the class was out of school (execute the event )... finally, school A received A message at (event type), and then the school bell rang (execute the corresponding event ). Of course, this is just an example. The content in it does not need to be taken seriously.
JQuery should certainly make good use of the features such as event bubbling. Always get ). This provides the necessary conditions for event delegation.
Imagine entrusting event source a to trigger the processing of the click event to its ancestor Node B. When node a triggers the click Event and the event bubbles to Node B, Node B also triggers the click event. Then, when Node B sees a delegate event in the event list, this delegate event stores the selector of the delegate node. If the matched node of this selector is event source a, B immediately executes this delegate event (of course, jQuery is more complex, if the delegate node is a node between a and B and the event type is the same as the trigger event type, the Delegate will be executed ).
Example
<style> #big{ width: 400px; height: 400px; background-color: #00f; } #middle{ width: 200px; height: 200px; background-color: #000; } #small{ width: 100px; height: 100px; background-color: #f00; }</style><div id="big"><div id="middle"><div id="small"></div></div></div>
<script> document.getElementById('big').onclick = function(){console.log("big clicked!")} document.getElementById('middle').onclick = function(){console.log("middle clicked!")} document.getElementById('small').onclick = function(){console.log("small clicked!")}</script>
Click the smallest red block (# small ). The execution result is as follows:
B. jQuery event delegation Process
In the previous chapter, we analyzed the event binding when we analyzed jQuery. event. add, and then extracted some of the bound source code.
If (! (EventHandle = elemData. handle )){EventHandle= ElemData. handle = function (e) {// when an event is called and the page has been uninstalled, discard the second event of jQuery. event. trigger (), return typeof jQuery! = Core_strundefined &&(! E | jQuery. event. triggered! = E. type )? JQuery. event. dispatch. apply (eventHandle. elem, arguments): undefined;}; // use elem as a feature of the handle function to prevent memory leakage caused by non-local ie events. eventHandle. elem = elem ;}
...
// Non-custom events. if the special event processor returns false, only addEventListener/attachEvent if (! Special. setup | special. setup. call (elem, data, namespaces, eventHandle) === false) {// bind the global event if (elem. addEventListener) {elem. addEventListener (type,EventHandle, False);} else if (elem. attachEvent) {elem. attachEvent ("on" + type,EventHandle);}}
The event bound to elem is handled by eventHandle. When eventHandle is executed, the event scheduling jQuery. event. dispatch is actually executed. The event scheduling process is actually the process for handling delegated events, because the node's response processing will be appended to the delegate processing list.
The event scheduling process is
1. Construct a writable jQuery. event object from the local Event object event. Replace the local event object in the passing parameter with this object.
// Construct a writable jQuery. Eventevent = jQuery. event. fix (event) from the local event object );
...
// Use the corrected jQuery. Event instead of the (read-only) local Event
Args [0] = event;
Event. delegateTarget = this;
The structure of the local event is as follows:
The jQuery. Event structure of the new Event object constructed using local events is as follows:
The value of the originalEvent attribute is the local event object. Many properties of the constructed event object are extracted directly from the local event object.
2. Obtain the event processing list of the corresponding event type in the current node Cache
handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
In the event processing list, the sequence of delegated event processing is prior, and finally the event processing that is directly bound to the current node.
3. Use the current node to replace the caller of jQuery. event. handlers and execute it to obtain the required delegated processing function Queue (this queue will end with a Processing event bound to the node itself)
// The designated event handling team. The master uses the event.tar get event source node to repeatedly search for the parent node. // check whether some nodes and handlerQueue = jQuery in the node corresponding to the selector in handlers. event. handlers. call (this, event, handlers );
Analyze in detail how to obtain the required delegated processing function queue in jQuery. event. handlers.
JQuery. event. handlers first extracts the delegate event and puts it in the handlerQueue of the processing queue.
The search process is: first retrieve the event source cur = event.tar get; then, when the event source is determined to have a delegate processing, query it from its ancestor node, traverse whether the Specified Response node (specified by selector of the delegate event processing object) in the delegate event list contains the queried node [handleObj. needsContext? JQuery (sel, this ). index (cur)> = 0: jQuery. find (sel, this, null, [cur]). length]. If it contains, the delegate is added to the event processing queue handlerQueue. The source code is as follows:
If (delegateCount & cur. nodeType &&(! Event. button | event. type! = "Click") {// bubble parent node, locate the matched delegate event and save it to the handlerQueue queue for (; cur! = This; cur = cur. parentNode | this) {if (cur. nodeType = 1 & (cur. disabled! = True | event. type! = "Click") {matches = []; for (I = 0; I <delegateCount; I ++ ){
// Avoid conflicts with the Object. prototype attribute (#13203)
Sel = handleObj. selector + "";
If (matches [sel] === undefined ){
Matches [sel] = handleObj. needsContext?
JQuery (sel, this). index (cur)> = 0:
JQuery. find (sel, this, null, [cur]). length;
}
If (matches [sel]) {
Matches. push (handleObj );
}
}
// Add a delegate to the queue
If (matches. length ){
HandlerQueue. push ({elem: cur, handlers: matches });
}
}
}
}
Finally, the process of binding directly to the current node is also pushed into execution.
// Add directly bound events to the handlerQueue queue if (delegateCount
4. Execute the processing function in the event processing queue handlerQueue
// Run the proxy first. They may prevent bubbling. We can use this I = 0; while (matched = handlerQueue [I ++]) &! Event. isPropagationStopped () {event. currentTarget = matched. elem; j = 0; while (handleObj = matched. handlers [j ++]) &! Event. isImmediatePropagationStopped () {// conditions for triggering an event: 1) No namespace, or // 2) has a subset of namespaces or is equal to those boundary events (either of them can have no namespace) if (! Event. namespace_re | event. namespace_re.test (handleObj. namespace) {event. handleObj = handleObj; event. data = handleObj. data; // execute ret = (jQuery. event. special [handleObj. origType] | {}). handle | handleObj. handler ). apply (matched. elem, args); if (ret! = Undefined) {if (event. result = ret) = false) {event. preventDefault (); event. stopPropagation ();}}}}}
Note that. The constructed new event object event is the event object (event. currentTarget can testify), but when the process is delegated, the event object is converted to the event object (event. currentTarget = matched. elem;) to ensure the correctness of the event objects called in the delegate event processing. For example, the preceding html is used, but the Execution Code is changed
function dohander(e){ alert("dohander");};function dot(e){ e.stopPropagation(); alert("dot");};
$(document).on("click",'#big',dohander).on("click",'#middle',dot).on("click",'#small',dohander);
The "# middle" Node delegates the document node to process the dot. The dot contains a piece of code e. stopPropagation (). When you click the "# middle" node, the event bubbles to the document. If the event is not converted to the event of the delegated node before the dot is executed, the blocking bubble does not prevent the event of the "# middle" node from bubbling, instead, it prevents event bubbles on the document node.
Of course, in fact, when processing the delegated event, the event has been bubbling to the entrusted node document. We cannot block nodes that have already been bubbling. For example, the "# big" node has already triggered the click event. However, we can simulate and block the bubble processing for the list of delegated event processing tasks delegated to the document node. Click the "# middle" node, and the event bubbles to the document. Execute jQuery. event. handlers to obtain the delegate event processing list.
// The delegate event processing of the "# small" node has been processed by jQuery. event. handlers filters out handlerQueue = ["# middle" Node delegate event processing dot, "# big" Node delegate event processing dohander];
Execute e. stopPropagation () in dot, and stopPropagation is also overloaded. The source code is as follows:
stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; if ( !e ) { return; } // If stopPropagation exists, run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } // Support: IE // Set the cancelBubble property of the original event to true e.cancelBubble = true; }
Here, this. isPropagationStopped = returnTrue; the event is marked as blocking bubbles. After the dot is executed, go to the next delegate for handerQueue execution""# Big" Node delegate event processing dohander", Here is a judgment
while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() )
Event. isPropagationStopped () = returnTrue () = (function returnTrue () {return true ;}) (); blocks access to while to execute the next delegate. OK. All subsequent delegate events are blocked for processing. Therefore, only the dot function is executed when you click the "# middle" node.
Stops the bubble process.
The following uses a more complete example to describe the js Code.
function dohander(e){ alert("dohander"); }; function dot(e){ e.stopPropagation(); alert("dot"); }; function doBigOnly(e){ alert("big"); } $(document).on("click",'#big',dohander) .on("click",'#middle',dot) .on("click",'#small',dohander); $('#big').click(doBigOnly);
Click the "# middle" node. The execution result is: alert ("big") is displayed, and alert ("dot") is displayed ")
Process Analysis:
Click "# middle", "# middle", and no event is bound.
Event bubbles to "# big", "# big" is bound to the Display alert ("big") that handles direct doBigOnly rows ").
Event bubbles to the body,
Then bubble to html,
Finally, bubble to document. There is a binding event on the document, and the "# small" delegate processing is filtered out based on the event source. The remaining two delegates ("# middle" and "# big") need to be processed. First process the "# middle" delegate to process the dot, and directly Display alert ("dot") to prevent event bubbles. Subsequent delegation is blocked.
Complete.
If you think this article is good, click [recommendation] in the lower right corner ]!