Event Analysis Based on jQuery source code analysis

Source: Internet
Author: User

The operations on events are nothing more than addEvent, fireEvent, and removeEvent. Generally, lib extends the functions provided by the browser to solve problems such as compatibility Memory leakage. The third problem is how to get the domReady status.
6.1 event packages

Browser event compatibility is a headache. The IE event is in the global window, while the mozilla event is passed in to the callback function as the event Source parameter. There are also many event handling methods.

Jquery provides an event package, which is a little simpler than other lib but is sufficient for use.

Copy codeThe 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 at the same time.
Var originalEvent = event; ①
Event = {originalEvent: originalEvent };
For (var I = this. props. length, prop; I ;){
Prop = this. props [-- I];
Event [prop] = originalEvent [prop];
}
Event [expando] = true;
// With preventDefault and stopPropagation, the clone will not run.
Event. preventDefault = function () {②
// Run on the original event
If (originalEvent. preventDefault)
OriginalEvent. preventDefault ();
OriginalEvent. returnValue = false;
};
Event. stopPropagation = function (){
// Run on the original event
If (originalEvent. stopPropagation)
OriginalEvent. stopPropagation ();
OriginalEvent. cancelBubble = true;
};
// Corrected timeStamp
Event. timeStamp = event. timeStamp | now ();
// Modify target
If (! Event.tar get) ③
Event.tar get = event. srcElement | document;
If (event.tar get. nodeType = 3) // The text node is the parent node.
Event.tar get = event.tar get. parentNode;
// RelatedTarget
If (! Event. relatedTarget & event. fromElement) ④
Event. relatedTarget = event. fromElement = event.tar get
? Event. toElement: event. fromElement;
// Calculate pageX/Y if missing and clientX/Y available
If (event. pageX = null & event. clientX! = Null) {6
Var doc = document.doc umentElement, 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) 7
? Event. charCode: event. keyCode ))
Event. which = event. charCode | event. keyCode;
  
// Add metaKey to non-Mac browsers
If (! Event. metaKey & event. ctrlKey) Success
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) Success
Event. which = (event. button & 1? 1: (event. button & 2
? 3: (event. button & 4? 2: 0 )));
Return event;
},

In code ① above, the original event reference is retained and the original event is cloned. Package 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 and pass it up.

③ The target is corrected, and srcElement is used in IE. For text node events, the target should be uploaded to its parent node.

④ RelatedTarget is only useful for mouseout and mouseover. In IE, the Target variables "to" and "from" are divided, which are not separated in mozilla. To ensure compatibility, use relatedTarget.

6 is the Coordinate Position of the event. This is relative to page. If the page can be scroll, add scroll to its client. In IE, the border of the default 2px body should be subtracted.

7. Press the keyboard event key to unify the attribute of event. which. In Ext, ev. charCode | ev. keyCode | 0; cursor is used to unify event. which by pressing the mouse event. CharCode and ev. keyCode are character keys and not character keys. Use the & method to handle the compatibility of the hosts. Ext solves compatibility issues through the following three lines.

Var btnMap = Ext. isIE? {1:0,}: (Ext. isSafari? {1:0, }:{,}); this. button = e. button? BtnMap [e. button]: (e. which? E. which-1:-1 );

① (3) (4) (6) (7)

6.2 event handling

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

6.2.1 Register

For event registration, jquery provides bind, one, toggle, and hover methods for event registration. bind is the most basic method. One is the method for registering to run only once, and toggle registers to run the method alternately. Hover is a method for registering mouse float.Copy codeThe Code is as follows: bind: function (type, data, fn ){
Return type = "unload "? This. one (type, data, fn): this
. Each (function () {// fn | data, fn & data implements the data parameter dispensable
JQuery. event. add (this, type, fn | data, fn & data );
});},

In the Bind, the unload event can only be run once, and the others adopt the default registration method.

// Bind a one-time event handler function for each specific event that matches the element (such as click.
// On each object, the event handler function is executed only once. Other rules are the same as those of the bind () function.
// This event handler receives an event object, which can be used to prevent (browser) default behavior.
// If you want to cancel the default action and prevent event bubbles, the event handler function must return false.Copy codeThe 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-> current element
});
Return this. each (function (){
JQuery. event. add (this, type, one, fn & data );
});
},

One is similar to bind. Different calling jQuery. event. add makes a small adjustment to the registered event processing function. One calls jQuery. event. proxy to perform the event processing function passed in by the proxy. When an event triggers a function to call this proxy, the event is first deleted from the cache, and then the registered event letter is executed. Here is the closure application. We can use the closure to get reference of the event function registered by fn.

// A method that imitates a hover event (move the mouse over an object and remove this object.
// This is a custom method. It provides a "Keep in it" status for frequently used tasks.
// When you move the cursor over a matching element, the specified first function is triggered. When the mouse removes this element,
/Triggers the specified second function. In addition, it will be accompanied by detection of whether the mouse is still in a specific element (for example, an image in the div ),
// If yes, the system will remain in the "hover" state without triggering the removal event (corrected a common error in using the mouseout event ).
Hover: function (fnOver, fnOut ){
Return this. bind ('mouseenter', fnOver). bind ('mouseleave ', fnOut );
},

Hover is based on bind.

// Call the function after each click.
Toggle: function (fn ){
Var args = arguments, I = 1;
While (I <args. length) // assign GUID to each function
JQuery. event. proxy (fn, args [I ++]); // The modified content is still in args.
Return this. click (jQuery. event. proxy (fn, function (event) {// assign GUID this. lastToggle = (this. lastToggle | 0) % I; // The event of the previous function. preventDefault (); // block the default action
// Execute the nth function in the parameter. apply can use the array-like parameter.
Return args [this. lastToggle ++]. apply (this, arguments) | false;
}));
},

The parameters in Toggle can be multiple fn. First, generate the UUID. Then call the click method to register the callback for the proxy again. This function is run when an event is triggered. It calculates the function in the previous parameter execution. Then stop the default action. Then find the next function to run.

// Add common event methods for 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 event processing method, including the click called above. Here we can see that the bind is called for registration. Of course, the event can also be triggered through program implementation.

Many of the methods above are registration events, which are ultimately implemented by jQuery. event. add (); to complete registration. If we use the Dom0 or DOM1 event method, we will use elem. onclick = function () {} to register a processing function for an element event. The biggest drawback is that each event is only a processing function. The dom1 method has been improved. We can use elem. addEventListener (type, handle, false) to register multiple processing functions for element events.

This processing method is not perfect. If we only run this event once, it will be a little troublesome. In the event processing function, we finally need to perform elem. removeEventListener to cancel event listening. This may cause transaction problems. What if the first event handler triggers the event again before canceling the event listening?

You can also use a browser to register and process custom events. You cannot register the same handler for multiple events.Copy codeThe 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 cannot input a window. Copy the window first.
If (jQuery. browser. msie & elem. setInterval) elem = window;
// Assign a globally unique Id to 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 events value is not obtained, initialize data: {} ②.
Var events = jQuery. data (elem, "events") | jQuery. data (elem, "events ",{}),
// If the value of handle is not obtained, initialize data: function () {...} ③
Handle = jQuery. data (elem, "handle") | jQuery. data (elem, "handle ",
Function () {// process 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 the handle attribute to prevent memory leakage due to the absence of local events in IE.
Handle. elem = elem;
// Process multiple event names separated by spaces, 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 the handler functions bound to the type event of this element
Var handlers = events [type]; ⑤
If (! Handlers) {// The event queue is initialized if the handler function list is not found.
Handlers = events [type] = {};
// If the type is not ready, or if the setup of ready is executed, false is returned. 6
If (! JQuery. event. special [type] | jQuery. event. special [type]. setup
. Call (elem, data) === false) {// call the system's event function to register the event
If (elem. addEventListener) elem. addEventListener (type, handle, false );
Else if (elem. attachEvent) elem. attachEvent ("on" + type, handle );
}
}
// Save the form of the processor id and handler form attribute pairs in the handlers list,
// It also exists in events [type] [handler. guid.
Handlers [handler. guid] = handler; 7
// The identifier used to globally cache this event
JQuery. event. global [type] = true;
});
  
Elem = null; // prevents IE Memory leakage.
},
Guid: 1,
Global :{},

JQuery. event. add uses jQuery. data to organically and orderly combine event-related event names and processing functions and store them in the space corresponding to this element in jQuery. cache. Let's take an example to analyze the process of adding: Let's entertain jQuery (e1) below ). bind ("mouseover mouseout", fn0); jQuery (e1 ). bind ("mouseover mouseout", fn1) statement.

When jQuery (e1). bind ("mouseover mouseout", fn0);, ② ③ it is impossible to get the number from the cache. initialize it first. Cache: {e0000uuid: {events :{}, handle: fn }}. Then, the name of mouseover mouseout will be initialized at ⑤. Cache: {e0000uuid: {events: {mouseover :{}, mouseout :{}}, handle: fn }}. Register the handler in Section 6 with the browser event. Then, the handler function is added to the event name. Cache: {e0000uuid: {events: {mouseover: {fn0_uuid: fn0}, mouseout: {fn0_uuid: fn0 }}, handle: fn }}. Here we can see that the role of Using proxy to generate uuid for the function.

In jQuery (e1 ). when bind ("mouseover mouseout", fn1), ② obtain data from cache {e0000uuid: {events: {mouseover: {fn0_uuid: fn0}, mouseout: {fn0_uuid: fn0}, and then get the reference of mouseover: {fn0_uuid: fn0}, mouseout: {fn0_uuid: fn0} In ⑤. Then, the handler is registered to the event name. Cache: {e0000uuid: {events: {mouseover: {fn0_uuid: fn0, fn0000uuid: fn1,}, mouseout: {fn0_uuid: fn0, fn0000uuid: fn1}, handle: fn }}.

A very important task of jQuery. event. add is to store the registered event functions in sequence. So that functions of the 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. When you click this element, the registered event processing function of the event is automatically triggered. However, we sometimes need to use the process sequence to simulate the event trigger and force trigger an event. In IE, we can use. fireEvent. For example: <form onsubmit = "a ()">, if the form. the submit () method does not trigger the onsumbit event actively. If necessary, it must be before the submit $ (": form") [0]. fireEvent ("onsubmit",) to trigger this event.

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

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

Prototype is implemented in this way. In jquery, the implementation method is a little different.Copy codeThe Code is as follows: trigger: function (type, data, fn ){
Return this. each (function (){
JQuery. event. trigger (type, data, this, true, fn );
});},

Trigger has three parameters. The data parameter provides real-time transmission for the registered event function. If preventDefault exists in data [0], data [0] can be used as the space for Custom Event packages. Fn is an instant and out-of-the-box event processing method. That is, if an event is not registered, you can pass in a processing function to process the event. If it has already been registered, it will be executed after the original event processing function.

// This method triggers all bound handler functions of the specified event type. However, the default browser action is not executed.
TriggerHandler: function (type, data, fn ){
Return this [0] & jQuery. event. trigger (type, data, this [0], false, fn );
},

TriggerHandle sets the donative parameter of jQuery. event. trigger to false to prevent the execution of the browser's passive processing method. It is a little different from trigger, but it only processes the first element of the jquery object.

The preceding two methods call jQuery. event. trigger to complete the task:Copy codeThe Code is as follows: trigger: function (type, data, elem, donative, extra ){
Data = jQuery. makeArray (data); // data can be {xx: yy}
// GetData is supported! In this form, exclusive = true indicates that
// All functions of the event are executed in different types of namespaces.
If (type. indexOf ("! ")> = 0) {①
Type = type. slice (0,-1); var exclusive = true;
}
If (! Elem) {// process the Global fire event ②
If (this. global [type])
JQuery. each (jQuery. cache, function (){
// Find all the elements that register the event from the cache and trigger the processing function of the event.
If (this. events & this. events [type])
JQuery. event. trigger (type, data, this. handle. elem );
});
} Else {// process the fire event of a single element event ③
If (elem. nodeType = 3 | elem. nodeType = 8) return undefined;
Var val, ret, fn = jQuery. isFunction (elem [type] | null ),
// If the data parameter is not passed into the browser's event object, the event variable is true.
// If the data parameter itself is a group, true is used when the first element is not the event object of the browser.
// True for event. That is, if no event is passed in, a forged event object is created and data [0] exists.
Event =! Data [0] |! Data [0]. preventDefault;
// Construct a forged event object without passing in the event object.
If (event) {// save to the first ④ in the array
Data. unshift ({type: type, target: elem,
PreventDefault: function () {}, stopPropagation:
Function () {}, timeStamp: now ()});
Data [0] [expando] = true; // you do not need to modify the forged event object.
}
Data [0]. type = type; // prevents event name errors
// Perform the classification (namespace) of the event registration function. Not all.
If (exclusive) data [0]. exclusive = true;
  
// Unlike traditional processing methods such as prototype, fireEvent is not used.
// Use the event handling method registered to the browser event by fire.
// There are three steps. First, the fire registers the event through jQuery. event. add. This event
// It may be a Custom Event (not registered to a browser event ).
// The second step is the local processing function of the event registered by fire using the elem. onclick method.
// Step 3 is the default fire event processing method (registered in the local onclick Mode)
// If it does not exist ).
// Events that are registered using jQuery. event. add are triggered here,
Var handle = jQuery. data (elem, "handle"); ⑤
If (handle) val = handle. apply (elem, data); // here data is divided into multiple parameters
// The processing trigger registers the local processing method through elem. onfoo = function,
// But it is not triggered for links's. click (), this will not be executed through addEvent
// Event handling method registered by the method.
If ((! Fn | (jQuery. nodeName (elem, 'A') & type = "click") ⑥
& Elem ["on" + type] & elem ["on" + type]. apply (elem, data) === false)
Val = false;
// The first few of the additional function parameters are given through data. Here, we will remove the forged event.
// Its last parameter is the result returned by a series of event processing functions, which are generally bool values.
// This function can process a scanning task based on the result.
If (event) data. shift ();
// Processing triggers the processing of the function specified by extra.
If (extra & jQuery. isFunction (extra) {7
Ret = extra. apply (elem, val = null? Data: data. concat (val ));
// If this function returns a value, the trigger return value is its return value.
// The last return value of the series event processing function. Generally bool
If (ret! = Undefined) val = ret;
}
// Trigger the default local event method, which is in the absence of event registration such as. onclick
// When the previous execution event processing function return value is not false, it will be executed.
// It can also control whether or not to execute through the donative command.
// For example, this. submit () can be used in form to submit form.
If (fn & donative! = False & val! = False success
&&! (JQuery. nodeName (elem, 'A') & type = "click ")){
This. triggered = true;
Try {elem [type] (); // for some hidden elements, IE reports an error
} Catch (e ){}
}
This. triggered = false;
}
Return val;
},

The fire event Method of Jquery is totally different from the implementation in prototype. Ext and YUI do not provide a method to force the event to be triggered. For general thinking, the fireEvent or dispatchEvent method should be used to trigger browser events.

But jquery uses a different method. Events registered through jquery. event. add (whether custom or registered to browser events) are stored in a cache corresponding to the element and event name. In the browser trigger, this is useless. However, it is used to obtain the corresponding event processing function from the cache when the program is forced to trigger. In this case, the browser is discarded. You can also execute some custom event functions here. For example, place ⑤.

For event functions registered in the form of click or elem. onclick = function () {} in html tags. In section 6, it can use the callback function in the form of execution elements such as onclick. Only one function can be registered using this dom0 method.

Sometimes, if there is no event processing function such as onclick, the browser will execute the default processing function. For example, form. submit (). The default event processing can also be controlled by the donative parameter.

The program forces the event to be triggered manually. One problem is how the event is generated, that is, no browser generates the event and passes it into the function. Prototype uses the newly generated dataavailable event. Such events have no effect. Jquery also uses the fake method to forge an event. For example, ④. It is better than prototype events because it can pass in the required event through the parameters of the trigger function. Prototype is not supported.

Through the above analysis, we can see that Jquery builds the trigger function by simulating the execution process of the browser trigger event. First, execute the dom1 method (addEvent) Registration event, then execute the dom0 method registration event, and finally see whether to execute the default event processing.

In section 7, we can see that trigger may also pass in callback functions and parameters to judge and process the results of the executed event processing function, forming a new result returned through the trigger function. This is useful sometimes.

In addition, it can also classify the event processing functions (namespace), and call the processing functions of different types of events (through jquery. event. add ). The processing of this classification is implemented in handle.Copy codeThe Code is as follows: handle: function (event ){
// Return undefined or false
Var val, ret, namespace, all, handlers;
// Modified the input parameter, which is referenced here.
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 and namespace do not exist.
// When event. exclusive does not exist or is false, all = true.
All =! Namespace &&! Event. exclusive;
// Find the list of handler functions for cached event names in element events
Handlers = (jQuery. data (this, "events") ||{}) [event. type];
For (var j in handlers) {// execute each processing function
Var handler = handlers [j];
// Filter the functions by class
If (all | handler. type = namespace ){
// Input references and delete them later
Event. handler = handler;
Event. data = handler. data; // added when you add
Ret = handler. apply (this, arguments); // execute the event processing function
If (val! = False)
Val = ret; // If a processing function returns false, this function returns false.
If (ret = false) {// do not execute the default action of the browser
Event. preventDefault ();
Event. stopPropagation ();
}
}
}
Return val ;}

Related Article

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.