Home  >  Article  >  Web Front-end  >  Event analysis of jQuery source code analysis_jquery

Event analysis of jQuery source code analysis_jquery

WBOY
WBOYOriginal
2016-05-16 18:25:271246browse

The operation of events is nothing more than the three event methods addEvent, fireEvent, and removeEvent. Generally, lib will make some extensions to the functions provided by the browser to solve problems such as compatibility memory leaks. The third question is how to get the status of domReady.
 6.1 Event package

Browser event compatibility is a headache. IE's event is under the global window, while Mozilla's event is that the event source parameters are passed into the callback function. There are many other event handling methods as well.

Jquery provides an event package, which is a bit simpler than what other libs provide, but it is enough to use.

Copy code The code is as follows:

//Wrap the event.
Fix: function(event) {
if (event[expando] == true) return event;//Indicates that the event has been wrapped
//Save the original event and clone one.
var originalEvent = event; ①
event = { originalEvent : originalEvent};
for (var i = this.props.length, prop;i;) {
-i];
  event[prop] = originalEvent[prop];
  } 
  event[expando] = true; event.preventDefault = function() { ②
    // Run on the original event
   if (originalEvent.preventDefault)
   originalEvent.preventDefault();
  originalEvent.returnValue = false;
  } ;
Event.stopPropagation = function() {
🎜>   };
   // Correct timeStamp
  event.timeStamp = event.timeStamp || now();
  // Correct target
  if (!event.target)    ③
  event. target = event.srcElement || document;  
 if (event.target.nodeType == 3)//The text node is the parent node.
Event.target = event.target.parentNode;
// relatedTarget
if (!event.relatedTarget && event.fromElement) event.relatedTarget = event.fromElement == event.target
    ? event.toElement : event.fromElement;
   // Calculate pageX/Y if missing and clientX/Y available
  if (event.pageX == null && event.clientX != null) { ⑥
var doc = document.documentElement, body = document.body;
event.pageX = event.clientX
(doc && doc.scrollLeft || body && body.scrollLeft || 0)
- (doc .clientLeft || 0);
event.pageY = event.clientY
(doc && doc.scrollTop || body && body.scrollTop || 0)
- (doc.clientTop || 0);
   }
 
   // Add which for key events
  if (!event.which && ((event.charCode || event.charCode === 0) ⑦
       ? event.charCode : event.keyCode))
  event.which = event.charCode || event.keyCode;
 
  // Add metaKey to non-Mac browsers
  if (!event.metaKey && event.ctrlKey )       ⑧
   event.metaKey = event.ctrlKey;
  // Add which for click: 1 == left; 2 == middle; 3 == right
  // Note: button is not normalized, so don't use it
  if (!event.which && event.button)  ⑨
   event.which = (event.button & 1 ? 1 : (event.button & 2
    ? 3 : (event .button & 4 ? 2 : 0)));
  return event;
},



The above code retains the reference of the original event at ① and clones the original event. Wrap on this clone event. ② Run the preventDefault and stopPropagation methods on the original event to determine whether to prevent the default event action from occurring and whether to stop the bubbling event from being passed upward.

③The target is corrected. srcElement is used in IE. At the same time, for text node events, the target should be passed to its parent node.

 ④ relatedTarget is only useful for mouseout and mouseover. In IE, it is divided into two Target variables, to and from, but in Mozilla, they are not separated. In order to ensure compatibility, relatedTarget is used to unify them.

 ⑥ is the coordinate position of the event. This is relative to page. If the page can be scrolled, scroll must be added to its client. In IE, the default 2px body border should also be subtracted.

The ⑦ point is to unify the keys of the keyboard event to the attribute of event.which. The implementation in Ext is ev.charCode || ev.keyCode || 0; ⑨ is to unify the mouse event keys on event.which. One of charCode and ev.keyCode is a character key, and the other is not a character key. ⑨ Use & method to handle compatibility. Ext resolves compatibility issues with the following three lines.

var btnMap = Ext.isIE ? {1:0,4:1,2:2} : (Ext.isSafari ? {1:0,2:1,3:2} : {0:0 ,1:1,2:2}); this.button = e.button ? btnMap[e.button] : (e.which ? e.which-1 : -1);

  ①②③④⑤⑥⑦⑧⑨⑩

 6.2 Event processing

 Jquery provides some methods for register, remove, and fire events.

 6.2.1 Register

For registering events, jquery provides four methods of registering events: bind, one, toggle, and hover. Bind is the most basic method. One is a method that is registered to run only once, and toggle is a method that is registered to run alternately. Hover is a method of registering mouse hovering.
Copy code The code is as follows:

bind : function(type, data, fn) {
return type == "unload" ? this.one(type, data, fn) : this
.each(function() {// fn || data, fn && data realizes that the data parameter is optional
    jQuery.event.add(this, type, fn || data, fn && data);
   }); },



Bind for unload Events can only be run once, and other events use the default registration method.

// Bind a one-time event handler function to a specific event (like click) of each matching element.
//On each object, this event handler will only be executed once. Other rules are the same as bind() function.
// This event handler will receive an event object, which can be used to prevent the default behavior (browser).
// If you want to cancel the default behavior and prevent the event from bubbling, this event handling function must return false.
Copy code The code is as follows:

  one : function(type, data, fn) {
var one = jQuery.event.proxy(fn || data, function(event) {
jQuery(this).unbind(event, one);
return (fn || data).apply(this , arguments);/this->The current element
  });
   return this.each(function() {
    jQuery.event.add(this, type, one, fn && data);
   });
  },

One and bind are basically the same. The difference is that when calling jQuery.event.add, the registered event processing function is slightly adjusted. One called jQuery.event.proxy to proxy the incoming event processing function. When an event triggers a call to this agent's function, the event is first deleted from the cache, and then the registered event function is executed. Here is the application of closure, through which the reference of the event function registered by fn is obtained.

//A method to simulate hover events (mouse moves over an object and moves out of the object).
//This is a custom method that provides a "keep in" state for frequently used tasks.
//When the mouse moves over a matching element, the specified first function will be triggered. When the mouse moves out of this element,
/ will trigger the specified second function. Moreover, it will be accompanied by detection of whether the mouse is still in a specific element (for example, an image in a div),
// If so, it will continue to remain in the "hover" state without triggering the move-out event (Fixed a common error using mouseout events).
Hover: function(fnOver, fnOut) {
return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
},



Hover is based on bind.

//Calling functions in sequence after each click.
toggle: function(fn) {
var args = arguments, i = 1;
while (i < args.length)//Each function is assigned a GUID
jQuery.event.proxy( fn, args[i ]);//The modified one is still in args
return this.click(jQuery.event.proxy(fn, function(event) {//Assign GUID  this.lastToggle = (this.lastToggle || 0) % i; // Previous function event.preventDefault(); // Prevent default action
// Execute the function in the parameters, apply can use array-like parameters
return args[this.lastToggle].apply(this,arguments)||false;
 }));
 },

  The parameters in Toggle can be multiple fn. First code them to generate UUIDs. Then call the click method to register the callback of the agent again. This function is run when the event is triggered. It first calculates the last time the function in the parameter was executed. Then prevent the default action. Then find the next function to run.

//Add commonly used event methods to jquery objects
jQuery.each(
  ("blur, focus, load, resize, scroll, unload, click, dblclick,"
"mousedown ,mouseup,mousemove,mouseover,mouseout,change,select,"
"submit,keydown,keypress,keyup,error").split(","),
function(i, name) {jQuery.fn [name] = function(fn) {
     return fn? this.bind(name, fn) : this.trigger(name);
   };});

 Jquery adds a common The event handling method includes the click called above. It can be seen here that bind is still called to register. Of course, events can also be triggered through programs.



Many of the above methods are to register events, which ultimately fall into jQuery.event.add(); to complete the registration function. If we use the event method of Dom0 or DOM1, we will use elem.onclick=function(){} to register a handler function for a certain event of the element. The biggest disadvantage of this is that each event is just a processing function. There is an improvement in the dom1 method. We can use elem.addEventListener(type, handle, false) to register multiple handler functions for element events.

This method of processing is not perfect. It would be a bit troublesome if we only run this event once. We need to perform elem.removeEventListener at the end of the event processing function to cancel the event listening. Doing so may cause business problems. What if the first event handling function is triggered again before canceling the event listening?

There is also the browser method, which does not support the registration and processing of custom events, and cannot register the same handler function for multiple events.
Copy code The code is as follows:

jQuery.event = {//Add event to an element.
add : function(elem, types, handler, data) {
if (elem.nodeType == 3 || elem.nodeType == 8) return; // Blank node or comment
  // IE It cannot be passed into window, so copy it first.
if (jQuery.browser.msie && elem.setInterval) elem = window;
// Assign a globally unique Id to the handler
if (!handler.guid) handler.guid = this.guid;
// Attach data to handler.data
if (data != undefined) {            ①
var fn = handler;
handler =this.proxy(fn,function(){return fn .apply(this,arguments);});
handler.data = data;
 }
// Initialize the events of the element. If the value in events is not obtained, initialize data: {}  ②
var events =jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),
// If the value in handle is not obtained, initialize data: function() {....}   ③
handle = jQuery.data(elem, "handle")|| jQuery.data(elem, "handle" ,
function() {//Handle the second event of a trigger and call an event after the page has been unloaded.
  if (typeof jQuery != "undefined"&& !jQuery.event.triggered)
Return jQuery.event.handle.apply(//callee.elem=handle.elem
arguments.callee.elem, arguments);
});
// Add elem as handle attribute, Prevent memory leaks in IE due to no local Event.
handle.elem = elem;
// Use spaces to separate multiple event names, such as jQuery(...).bind("mouseover mouseout", fn);
jQuery.each(types. split(/s /), function(index, type) {   ④
  // Namespace events are generally not used.
var parts = type.split(".");type = parts[. 0];handler.type = parts[1];
// All handlers bound to the type event of this element
var handlers = events[type]; ⑤
if (!handlers) {// Initialize the event queue if the handler list is not found
handlers = events[type] = {};
// If type is not ready, or the setup execution of ready returns false ⑥
if (!jQuery.event. special[type]|| jQuery.event.special[type].setup
  .call(elem, data) === false) {// Call the system event function to register the event
if(elem.addEventListener )elem.addEventListener(type,handle,false);
else if (elem.attachEvent)elem.attachEvent("on" type, handle);
   }
}
// Put the handler The form of the id and handler form attribute pair is stored in the handlers list,
// also exists in events[type][handler.guid]
handlers[handler.guid] = handler;      ⑦
/ / Globally cache the usage identifier of this event
jQuery.event.global[type] = true;
});
 
 elem = null; // Prevent IE memory leaks.
 },
guid: 1,
global: {},



jQuery.event.add uses jQuery.data to organically and orderly combine the event names and processing functions related to the event and store them in the space corresponding to the element in jQuery.cache. Let's analyze the add process with an example: If we receive the following jQuery(e1).bind("mouseover mouseout", fn0);jQuery(e1).bind("mouseover mouseout", fn1) statement.

When jQuery(e1).bind("mouseover mouseout", fn0);, ②③ cannot get the number from the cache, so initialize it first. The cache at this time: {e1_uuid:{events:{},handle:fn}}. Then in ⑤ the mouseover mouseout name will be initialized. The cache at this time: {e1_uuid:{events:{ mouseover:{}, mouseout:{}},handle:fn}}. Register the handler function to the browser event at ⑥. Then ⑦ will add the handler function to the event name. The cache at this time: {e1_uuid:{events:{mouseover:{fn0_uuid:fn0},mouseout:{ fn0_uuid:fn0}},handle:fn}}. Here you can see the role of using proxy to generate uuid for the function.

When jQuery(e1).bind("mouseover mouseout", fn1), ②③ both get data from cache {e1_uuid:{events:{mouseover:{fn0_uuid:fn0},mouseout:{ fn0_uuid: fn0}}, and then get the references of mouseover:{fn0_uuid:fn0}, mouseout:{ fn0_uuid:fn0} in ⑤. Then ⑦ will register the handler function into the event name. The cache at this time: {e1_uuid:{events:{mouseover:{fn0_uuid:fn0, fn1_uuid:fn1,},mouseout:{ fn0_uuid:fn0, fn1_uuid:fn1}},handle:fn}}.

A very important task of jQuery.event.add is to store the registered event functions in an orderly manner. So that the functions of remove and fire events can be found.

//{elem_uuid_1:{events:{mouseover:{fn_uuid:fn1,fn_uuid1:fn2},
     //mouseout:{fn_uuid:fn1,fn_uuid1:fn2}},handle:fn}}

  6.2.2 trigger



  Registered events, such as onclick. Then when the user clicks on this element, the registered event handler of this event will be automatically triggered. But sometimes when we want to use a program to simulate the triggering of an event, we have to use forced triggering of an event. In IE we can use .fireEvent() to achieve this. For example:
, if the button submits the form using form.submit(), the onsumbit event will not be actively triggered. If necessary, $(" :form")[0].fireEvent("onsubmit",), this will trigger the event.

There are three steps in mozilla: var evt = document.createEvent('HTMLEvents');

evt.initEvent('change',true,true); t.dispatchEvent(evt) ;

It is implemented in this way in the prototype. So in jquery, its implementation is a little different.
Copy code The code is as follows:

trigger: function(type, data, fn) {
return this.each(function() {
 jQuery.event.trigger(type, data, this, true, fn);
   }); },

 Trigger has three types Parameters, the data parameter provides actual transmission for the registered event function. If preventDefault exists in data[0], data[0] can be used as a space for user-defined wrapping events. Fn can provide a ready-to-use event processing method for events. That is to say, the event can be processed by passing in the handler function without registering the event. If it has been registered, it will be executed after the original event handling function.

  //This method will trigger all bound handlers on the specified event type. But the browser default action will not be executed.
triggerHandler: function(type, data, fn) {
return this[0]&& jQuery.event.trigger(type,data,this[0],false,fn );
 },



  The triggerHandle prevents the execution of the browser default processing method by setting the donative parameter of jQuery.event.trigger to false. The difference between it and trigger is that it only processes the first element of the jquery object.

Both the above two methods call jQuery.event.trigger to complete the task:
Copy code The code is as follows :

trigger: function(type, data, elem, donative, extra) {
 data = jQuery.makeArray(data);//data can be {xx:yy}
 //Supports getData !In this form, exclusive = true means that all functions of the registered
// event of add will be executed according to the namespace classification.
if (type.indexOf("!") >= 0) {        ①
  type = type.slice(0, -1);var exclusive = true;
  }
if (! elem) {// Handle the global fire event ②
  if (this.global[type])
  jQuery.each(jQuery.cache, function() {
  // Find all registered files from the cache The element of the event, triggering the handler function of the event
if (this.events && this.events[type])
jQuery.event.trigger(type, data, this.handle.elem);
} ; = jQuery.isFunction(elem[type] || null),
// If the data parameter is not the browser's event object, the event variable is true.
// If the data parameter itself is a group , then it is true when the first element is not the browser's event object.
  //It is true for event. That is, if no event is passed in, a fake event object is first constructed and exists in data[0].
 event = !data[0] || !data[0].preventDefault;
  // Construct a fake event object without passing in the event object.
 if (event) {//The first one stored in the array ④
  data.unshift( { type : type, target : elem,
   preventDefault : function() {},stopPropagation :
function() {}, timeStamp : now() });
data[0][expando] = true; // No need to fix the fake event object
}
data[0].type = type; //Prevent event name errors
//The performance will be classified (namespace) execution of the event registration function. Not all.
  if (exclusive) data[0].exclusive = true;
 
  // Different from traditional processing methods such as prototype, fireEvent is not used
  // Fire is registered to the browser Event handling methods in events.
// There are three steps here. First, fire the event registered through jQuery.event.add. This event
// may be a custom event (not registered in the browser event).
// The second step is the local processing function of the event registered by fire through elem.onclick method
// The third step is the default event processing method of fire (registered by local onclick method
// does not exist).
// Here is the trigger event registered through jQuery.event.add,
 var handle = jQuery.data(elem, "handle");  ⑤
 if (handle)val = handle.apply( elem, data); //The data here is divided into multiple parameters
 //The processing is triggered by registering a local processing method such as elem.onfoo=function(),
 //But for links's .click() Not triggered, this will not execute the event processing method registered through addEvent
// method.
  if ((!fn || (jQuery.nodeName(elem, 'a') && type == "click")) ⑥
    && elem["on" type]&& elem["on" type] .apply(elem,data) === false)
 val = false;
//The first few additional function parameters are given through data. The forged events will be removed here.
//The last parameter is the result returned by a series of event processing functions, usually a bool value.
//This function can handle a finishing work based on this result.
 if (event) data.shift();
// Processing triggers the function processing given by extra.
if (extra && jQuery.isFunction(extra)) { ⑦
ret = extra.apply(elem, val == null ? data : data.concat(val));
//If this function If there is a return value, then the return value of the trigger is its return value
// If not, it is the last return value of the series of event processing functions. Generally bool
  if (ret !== undefined) val = ret;
  }
  // Trigger the default local event method, which is when there is no registered event such as .onclick
// Add the preceding It will be executed only if the return value of the execution event handler function is false.
//It can also be controlled through donative to control whether to execute.
//For example, this.submit() can be used in the form to submit the form.
if (fn && donative !== false && val !== false  ⑧
  && !(jQuery.nodeName(elem, 'a') && type == "click")) {
 this.triggered = true;
 try {elem[type](); //For some hidden elements, IE will report an error
  } catch (e) {}
  }
this.triggered = false;
 }
return val;
},




The method of Jquery’s fire event is completely different from the implementation in prototype. Ext and YUI do not provide a method to force trigger events. For general thinking, programs that trigger browser events should use the fireEvent or dispatchEvent method to run.

But jquery takes a different approach. For events registered through jquery.event.add (whether customized or registered to browser events), it is stored in a cache corresponding to the element and event name. In browser triggering, this has no effect. But it is to get the corresponding event processing function from the cache when it is forced to trigger by waiting for the program. At this time, browser events are discarded. Some custom event functions can also be executed here. Such as ⑤.

For event functions registered through html tags such as click or elem.onclick=function(){}. At ⑥, it just uses a callback function in the form of onclick to execute the element. Only one function can be registered through this dom0 method.

Sometimes, if there is no event handler such as onclick, the browser will execute the default handler. Such as form.submit(). It can be seen from ⑧ that such default event processing can also be controlled through the parameter donative.

The program manually triggers the event. One problem is how the event is generated, that is, there is no browser to generate the event and pass it into the function. Prototype uses the newly generated dataavailable event. Such events have little effect. Jquery also uses fake to forge events one by one, such as ④. Its advantage over prototype events is that it can pass in the required events through the parameters of the trigger function. Prototype cannot.

Through the above analysis, it can be seen that Jquery builds this trigger function by simulating the execution process of the browser's trigger event. First execute the event registered in dom1 mode (addEvent), then execute the event registered in dom0 mode, and finally see whether to execute the default event processing.

At ⑦, we can see that the trigger may also complete the judgment and processing of the results of the executed event processing function by passing in the callback function and parameters, and form a new result and return it through the trigger function. This is sometimes useful.



In addition to these, it can also classify event processing functions (namespace), and can call event processing functions of different categories at the appropriate time (via jquery.event.add to register). The processing of this classification is implemented in handle.
Copy code The code is as follows:

handle: function(event) {
// Return undefined or false
  var val, ret, namespace, all, handlers;
  // Modified the parameters passed in, here is the reference.
Event = arguments[0] = jQuery.event.fix(event || window.event);
// Namespace processing
namespace = event.type.split(".");
Event.type = namespace[0];
Namespace = namespace[1];
// all = true indicates that any handler, namespace does not exist, and
//event.exclusive does not exist or is false When, all=true.
All = !namespace && !event.exclusive;
// Find the handler list of event names cached in the element's events
handlers = (jQuery.data(this, " events") || {})[event.type];
for (var j in handlers) { // Each handler function is executed
var handler = handlers[j];
// Filter the functions by class
   if (all || handler.type == namespace) {
    // Pass in references in order to delete them later
   event.handler = handler;
  event.data = handler. data;//
added when adding ret = handler.apply(this, arguments);//Execute the event processing function
if (val !== false)
val = ret;// As long as one processing function returns false, this function will return false.
 if (ret === false) {//Do not perform the browser's default action
   event.preventDefault();
   event.stopPropagation ();
    }
    }
   }
  return val;  }
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn