Using a class library can easily solve compatibility issues. But what is the mechanism behind this? Let's talk about it a little bit.
First, DOM Level2 defines two functions for event processing: addEventListener and removeEventListener, both of which come from the EventTarget interface.
Copy codeThe Code is as follows:
Element. addEventListener (eventName, listener, useCapture );
Element. removeEventListener (eventName, listener, useCapture );
The EventTarget interface is usually implemented from the Node or Window interface, that is, the so-called DOM element.
For example, you can use addEventListener to add a listener for window.
Copy codeThe Code is as follows:
Function loadHandler (){
Console. log ('the page is loaded! ');
}
Window. addEventListener ('load', loadHandler, false );
RemoveEventListener can also be used to remove a listener. You only need to pay attention to the removed handle and the added handle.
Window. removeEventListener ('load', loadHandler, false );
If we live in the perfect world, it is estimated that the event function is over.
However, this is not the case. Because IE is unique, the attachEvent and detachEvent functions are defined using the msdhtml dom to replace addEventListener and removeEventListener.
There are many differences between functions, making the entire event mechanism very complex.
So what we need to do is actually to handle the differences in event processing between IE and w3c standards.
You can add or remove a listener under IE to write
Copy codeThe Code is as follows:
Function loadHandler (){
Alert ('the page is loaded! ');
}
Window. attachEvent ('onload', loadHandler); // Add a listener
Window. detachEvent ('onload', loadHandler); // remove the listener
From the appearance, we can see the difference between IE and w3c:
1. There is an additional "on" prefix before the event.
2. Remove the third parameter useCapture.
In fact, the real difference is far more than this. We will continue to analyze the differences later. So we can easily abstract a common function for the two differences.
Copy codeThe Code is as follows:
Function addListener (element, eventName, handler ){
If (element. addEventListener ){
Element. addEventListener (eventName, handler, false );
}
Else if (element. attachEvent ){
Element. attachEvent ('on' + eventName, handler );
}
Else {
Element ['on' + eventName] = handler;
}
}
Function removeListener (element, eventName, handler ){
If (element. addEventListener ){
Element. removeEventListener (eventName, handler, false );
}
Else if (element. detachEvent ){
Element. detachEvent ('on' + eventName, handler );
}
Else {
Element ['on' + eventName] = null;
}
}
The above functions have two points to note:
1. The w3c standard should be determined first for the first branch, because IE is gradually approaching the standard. The second branch monitors IE.
2. The third branch is left to browsers that neither add/remove EventListener nor attach Event are supported.
Performance Optimization
For the above functions, we use "RunTime" for monitoring. that is, branch monitoring is required for each binding event. we can change it to "before running" to determine the compatible function. instead of monitoring each time.
In this way, we need to use a DOM element for early detection. Here we choose document.doc umentElement. Why not use document. body? Because document.doc umentElement already exists when document does not have ready, and document. body does not exist before ready.
In this way, the function is optimized
Copy codeThe Code is as follows:
Var addListener, removeListener,
/* Test element */
DocEl = document.doc umentElement;
// AddListener
If (docEl. addEventListener ){
/* If 'addeventlistener 'exists on test element, define function to use 'addeventlistener '*/
AddListener = function (element, eventName, handler ){
Element. addEventListener (eventName, handler, false );
};
}
Else if (docEl. attachEvent ){
/* If 'tachevent' exists on test element, define function to use 'tachevent '*/
AddListener = function (element, eventName, handler ){
Element. attachEvent ('on' + eventName, handler );
};
}
Else {
/* If neither methods exists on test element, define function to fallback strategy */
AddListener = function (element, eventName, handler ){
Element ['on' + eventName] = handler;
};
}
// RemoveListener
If (docEl. removeEventListener ){
RemoveListener = function (element, eventName, handler ){
Element. removeEventListener (eventName, handler, false );
};
}
Else if (docEl. detachEvent ){
RemoveListener = function (element, eventName, handler ){
Element. detachEvent ('on' + eventName, handler );
};
}
Else {
RemoveListener = function (element, eventName, handler ){
Element ['on' + eventName] = null;
};
}
This avoids the need to judge each binding.
It is worth mentioning that. the above Code actually has two serious injuries. in addition to increasing the number of codes, some users have used the hard drive test code. Our basic ideas are broken. If document.doc umentElement has the add/remove Method. the element must be available (although in most cases ). but this is obviously not safe enough.
Insecure Detection
The following two examples show that in some cases this detection is not safe enough.
Copy codeThe Code is as follows:
// In Internet Explorer
Var xhr = new ActiveXObject ('Microsoft. xmlhttp ');
If (xhr. open) {}// Error
Var element = document. createElement ('P ');
If (element. offsetParent) {}// Error
For example, in IE7, typeof xhr. open = 'unknown '. For details, refer to feature-detection.
Therefore, the detection method we advocate is
Copy codeThe Code is as follows:
Var isHostMethod = function (object, methodName ){
Var t = typeof object [methodName];
Return (t = 'function' | t = 'object ')&&!! Object [methodName]) | t = 'unknown ';
};
In this way, the above optimization function is improved to this way again.
Copy codeThe Code is as follows:
Var addListener, docEl = document.doc umentElement;
If (isHostMethod (docEl, 'addeventlistener ')){
/*...*/
}
Else if (isHostMethod (docEl, 'tachevent ')){
/*...*/
}
Else {
/*...*/
}
Lost this pointer
This pointer processing. There is another difference between IE and w3c. In w3c, the pointer to the function is a DOM element bound to the handle. in IE, it always points to window.
Copy codeThe Code is as follows:
// IE
Document. body. attachEvent ('onclick', function (){
Alert (this = window); // true
Alert (this = document. body); // false
});
// W3C
Document. body. addEventListener ('onclick', function (){
Alert (this = window); // false
Alert (this = document. body); // true
});
This problem is not difficult to fix.
Copy codeThe Code is as follows:
If (isHostMethod (docEl, 'addeventlistener ')){
/*...*/
}
Else if (isHostMethod (docEl, 'tachevent ')){
AddListener = function (element, eventName, handler ){
Element. attachEvent ('on' + eventName, function (){
Handler. call (element, window. event );
});
};
}
Else {
/*...*/
}
We only need to use a packaging function. then, the internal Department uses the call method to correct the pointer. in fact, we should have seen that a problem is also secretly corrected here. in IE, the event is not passed through the first function, but is left in the global environment. so we often write event = event | window. event code. the modification is also made here.
Corrected these main problems. Our function seems to be much more robust. We can pause and perform a simple test to test three points.
1. Compatibility with various browsers 2. this pointer pointing to compatibility 3. Compatibility with event parameter transfer.
The test code is as follows:
<! DOCTYPE html PUBLIC "-// W3C // dtd xhtml 1.0 Transitional // EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ptml xmlns = "http://www.w3.org/1999/xhtml"> <pead> <title> Event Test UseCase </title> <meta http-equiv = "Content-Type" content = "text/html; charset = gb2312 "> </pead> <body> test text </body> </ptml>
[Ctrl + A select all Note: If you need to introduce external Js, You need to refresh it to execute]
We only need to call the method like this:
Copy codeThe Code is as follows:
AddListener (o, 'click', function (event ){
This. style. backgroundColor = 'blue ';
Alert((event.tar get | event. srcElement). innerHTML );
});
It can be seen that the 'click', this, and event are consistent with the browser. Is that all right?
In fact, this is only the first step in the long journey. Due to the harmonious Memory leakage in IE browser, our event mechanism should be considered much more complicated than above.
Let's take a look at the above Code to correct this pointer.
Element. attachEvent ('on' + eventName, function (){
Handler. call (element, window. event );
});
Element --> handler --> element easily forms a loop reference. Memory leakage in IE.
Release loop reference
The solution to memory leakage is to cut off the circular reference. that is, the reference of handler --> element is cut. it is easy to think of, and there are still many methods used by class libraries. it is to point all handler to null when window form unload.
The basic code is as follows:
Code
Copy codeThe Code is as follows:
Function wrapHandler (element, handler ){
Return function (e ){
Return handler. call (element, e | window. event );
};
}
Function createListener (element, eventName, handler ){
Return {
Element: element,
EventName: eventName,
Handler: wrapHandler (element, handler)
};
}
Function cleanupListeners (){
For (var I = listenersToCleanup. length; I --;){
Var listener = listenersToCleanup [I];
Litener. element. detachEvent (listener. eventName, listener. handler );
ListenersToCleanup [I] = null;
}
Window. detachEvent ('onunload', cleanupListeners );
}
Var listenersToCleanup = [];
If (isHostMethod (docEl, 'addeventlistener ')){
/*...*/
}
Else if (isHostMethod (docEl, 'tachevent ')){
AddListener = function (element, eventName, handler ){
Var listener = createListener (element, eventName, handler );
Element. attachEvent ('on' + eventName, listener. handler );
ListenersToCleanup. push (listener );
};
Window. attachEvent ('onunload', cleanupListeners );
}
Else {
/*...*/
}
That is, the listener is saved in an array. When window. unload is executed, all the loops point to null at a time. The reference is cut off.
This seems to be a good way to solve the memory leakage problem.
Avoid Memory leakage
We just had to relax. another shocking thing happened. bfcache is a page cache mechanism implemented by most mainstream browsers. this article introduces several terms that may cause cache invalidation.
The page uses an unload or beforeunload handler
The page sets "cache-control: no-store"
The page sets "cache-control: no-cache" and the site is HTTPS.
The page is not completely loaded when the user navigates away from it
The top-level page contains frames that are not cacheable
The page is in a frame and the user loads a new page within that frame (in this case, when the user navigates away from the page, the content that was last loaded into the frames is what is cached)
The first one is that our great unload will kill the page cache. the function of page cache is. every time we click forward and backward, the button will be read from the cache, and we don't need to request the server every time. this leads to a conflict...
We want to cache pages, but we have to cut off the loop reference of Memory leakage, but we cannot use the unload event...
Finally, you can only use the ultimate solution. This is to disable circular references.
This solution is also very difficult to describe carefully. However, if you have seen the earliest event function of DE, it is not difficult to understand it. To sum up, you need to do the following work.
1. Specify a unique uniqueID for each element.
2. Use an independent function to create a listener. However, this function does not directly reference the element to avoid circular reference.
3. The created listener is combined with an independent uid and eventName.
4. Trigger the encapsulated event handle through attachEvent.
After a series of analyses above, we get the final most perfect event function.
Copy codeThe Code is as follows:
(Function (global ){
// Determine whether a host attribute exists
Function areHostMethods (object ){
Var methodNames = Array. prototype. slice. call (arguments, 1 ),
T, I, len = methodNames. length;
For (I = 0; I <len; I ++ ){
T = typeof object [methodNames [I];
If (! (/^ (? : Function | object | unknown) $/). test (t) return false;
}
Return true;
}
// Obtain the unique ID
Var getUniqueId = (function (){
If (typeof document.doc umentElement. uniqueID! = 'Undefined '){
Return function (element ){
Return element. uniqueID;
};
}
Var uid = 0;
Return function (element ){
Return element. _ uniqueID | (element. _ uniqueID = 'uniqueid _ '+ uid ++ );
};
})();
// Get/set element flag
Var getElement, setElement;
(Function (){
Var elements = {};
GetElement = function (uid ){
Return elements [uid];
};
SetElement = function (uid, element ){
Elements [uid] = element;
};
})();
// Create a listener independently
Function createListener (uid, handler ){
Return {
Handler: handler,
WrappedHandler: createWrappedHandler (uid, handler)
};
}
// Event handle packaging function
Function createWrappedHandler (uid, handler ){
Return function (e ){
Handler. call (getElement (uid), e | window. event );
};
}
// Dispatch events
Function createDispatcher (uid, eventName ){
Return function (e ){
If (handlers [uid] & handlers [uid] [eventName]) {
Var handlersForEvent = handlers [uid] [eventName];
For (var I = 0, len = handlersForEvent. length; I <len; I ++ ){
HandlersForEvent [I]. call (this, e | window. event );
}
}
}
}
// Main function body
Var addListener, removeListener,
ShouldUseAddListenerRemoveListener = (
AreHostMethods(document.doc umentElement, 'addeventlistener ', 'removeeventlistener ')&&
AreHostMethods (window, 'addeventlistener ', 'removeeventlistener ')),
ShouldUseAttachEventDetachEvent = (
AreHostMethods(document.doc umentElement, 'tachevent', 'detachevent ')&&
AreHostMethods (window, 'tachevent', 'detachevent ')),
// IE branch
Listeners = {},
// DOM L0 branch
Handlers = {};
If (shouldUseAddListenerRemoveListener ){
AddListener = function (element, eventName, handler ){
Element. addEventListener (eventName, handler, false );
};
RemoveListener = function (element, eventName, handler ){
Element. removeEventListener (eventName, handler, false );
};
}
Else if (shouldUseAttachEventDetachEvent ){
AddListener = function (element, eventName, handler ){
Var uid = getUniqueId (element );
SetElement (uid, element );
If (! Listeners [uid]) {
Listeners [uid] = {};
}
If (! Listeners [uid] [eventName]) {
Listeners [uid] [eventName] = [];
}
Var listener = createListener (uid, handler );
Listeners [uid] [eventName]. push (listener );
Element. attachEvent ('on' + eventName, listener. wrappedHandler );
};
RemoveListener = function (element, eventName, handler ){
Var uid = getUniqueId (element), listener;
If (listeners [uid] & listeners [uid] [eventName]) {
For (var I = 0, len = listeners [uid] [eventName]. length; I <len; I ++ ){
Listener = listeners [uid] [eventName] [I];
If (listener & listener. handler === handler ){
Element. detachEvent ('on' + eventName, listener. wrappedHandler );
Listeners [uid] [eventName] [I] = null;
}
}
}
};
}
Else {
AddListener = function (element, eventName, handler ){
Var uid = getUniqueId (element );
If (! Handlers [uid]) {
Handlers [uid] = {};
}
If (! Handlers [uid] [eventName]) {
Handlers [uid] [eventName] = [];
Var existingHandler = element ['on' + eventName];
If (existingHandler ){
Handlers [uid] [eventName]. push (existingHandler );
}
Element ['on' + eventName] = createDispatcher (uid, eventName );
}
Handlers [uid] [eventName]. push (handler );
};
RemoveListener = function (element, eventName, handler ){
Var uid = getUniqueId (element );
If (handlers [uid] & handlers [uid] [eventName]) {
Var handlersForEvent = handlers [uid] [eventName];
For (var I = 0, len = handlersForEvent. length; I <len; I ++ ){
If (handlersForEvent [I] === handler ){
HandlersForEvent. splice (I, 1 );
}
}
}
};
}
Global. addListener = addListener;
Global. removeListener = removeListener;
}) (This );
So far, our entire event function has developed to a perfect level, but there are still some places we have not taken care of. We can only wonder that the difference between IE and w3c in event processing is too big.
Missing details
Even though hundreds of lines of code have corrected a compatible event mechanism, improvements are still needed.
1. Because the mshtml dom does not support the event mechanism and does not support the capture phase, let the third parameter be missing.
2. Event handle trigger sequence. Most browsers use FIFO (first-in-first-out), while IE only requires LIFO (first-in-first-out). In fact, the DOM3 draft has already explained specifies the order as FIFO.
Other details are different.
The whole article seems awkward to record your own ideas. But the relatively perfect event function still has a little reference value, hoping to help you a little.
If you have good comments and suggestions, please advise. Thank you.
Package and download code