Using class libraries makes it easier to resolve compatibility issues. But what is the underlying mechanism? Now let's just roll it out a little bit.
First, the DOM Level2 defines two functions AddEventListener and RemoveEventListener for event handling, both of which come from the Eventtarget interface.
Copy Code code as follows:
Element.addeventlistener (EventName, Listener, usecapture);
Element.removeeventlistener (EventName, Listener, usecapture);
The Eventtarget interface is usually implemented from the node or window interface. What is called the DOM element.
Then, for example, window can also be addeventlistener to add listening.
Copy Code code as follows:
function Loadhandler () {
Console.log (' The page is loaded! ');
}
Window.addeventlistener (' Load ', Loadhandler, false);
Removing the listener is also easy to do through RemoveEventListener, as long as you notice that the removed handle and the handle you add are referenced from a function.
Window.removeeventlistener (' Load ', Loadhandler, false);
If we live in the perfect world. Then the estimated event function is over.
But this is not the case. Because IE is unique. The attachevent and detachevent two functions were defined by the msdhtml DOM to replace AddEventListener and RemoveEventListener.
There are many differences between functions, which makes the whole event mechanism become extremely complicated.
So what we're going to do is actually shifting. Handle differences between IE browser and the standard for event handling.
Adding listening and removing listening under IE can be written like this
Copy Code code as follows:
function Loadhandler () {
Alert (' The page is loaded! ');
}
Window.attachevent (' onload ', loadhandler); Add listening
Window.detachevent (' onload ', loadhandler); Remove listening
From the appearance, we can see the two differences between IE and the Internet:
1. The event was preceded by an "on" prefix.
2. Removed the third parameter of Usecapture.
In fact, the real difference is much more than that. We'll continue our analysis later. So for these two differences, it's easy to abstract a common function
Copy Code code 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 function has two places to note:
1. The first branch is best measured by the standards of the consortium. Because IE is also gradually approaching the standard. The second branch monitors ie.
2. The third branch is reserved for browsers that neither support (Add/remove) EventListener nor support (Attach/detach) event.
Performance optimization
For the above function we use "run-time" monitoring. That is, each binding event requires branch monitoring. We can change it to "run before" to determine the compatibility function. Without each monitoring.
So we need to use a DOM element to detect in advance. Here we choose the document.documentelement. Why not document.body? Because document.documentelement existed when the document was not ready, and document.body did not exist before ready.
This function is optimized to
Copy Code code as follows:
var addListener, RemoveListener,
/* Test Element * *
Docel = document.documentelement;
AddListener
if (Docel.addeventlistener) {
/* if ' addeventlistener ' exists on test element, the Define function to use ' addeventlistener ' *
AddListener = function (element, EventName, handler) {
Element.addeventlistener (EventName, Handler, false);
};
}
else if (docel.attachevent) {
/* if ' attachevent ' exists on test element, the Define function to use ' attachevent ' *
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 code above is also a two-point mishap. In addition to the increase in the number of code, there is one point is the use of hard coding speculation. The code above basically means to determine. If Document.documentelement has add/ The Remove method. Then element must be possessed (though most of the case). But this is clearly not safe enough.
Unsafe detection
The following two examples illustrate that this test is not safe enough in some cases.
Copy Code code 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: Under IE7 typeof xhr.open = = ' Unknown '. Detailed reference to Feature-detection
So the way we're advocating this is
Copy Code code as follows:
var Ishostmethod = function (object, methodname) {
var t = typeof Object[methodname];
return (t = = ' function ' | | t = = = ' object ') &&!! Object[methodname]) | | t = = = ' Unknown ';
};
So the optimization function above us.
Copy Code code as follows:
var addListener, docel = document.documentelement;
if (Ishostmethod (Docel, ' AddEventListener ')) {
/* ... */
}
else if (Ishostmethod (Docel, ' attachevent ')) {
/* ... */
}
else {
/* ... */
}
The missing this pointer
This pointer is handled. There are also differences between IE and the Internet. In the context of the consortium, a pointer to a function is a DOM element that is bound to the handle. But IE always points to window.
Copy Code code 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
});
It's not too much trouble to fix this problem.
Copy Code code as follows:
if (Ishostmethod (Docel, ' AddEventListener ')) {
/* ... */
}
else if (Ishostmethod (Docel, ' attachevent ')) {
AddListener = function (element, EventName, handler) {
Element.attachevent (' on ' + eventName, function () {
Handler.call (element, window.event);
});
};
}
else {
/* ... */
}
We just need to use a wrapper function. And then internally handler the pointer with call. In fact, everyone should also see, here also secretly fixed a problem is. IE event is not passed through the first function, but is left in the global. So we often write the event = Event | | Window.event code like this. There are also amendments here.
Fixed a few of these major problems. Our function seems to be a lot more robust. We can pause and do a simple test, test three.
1. The browsers are compatible with 2. This pointer points to compatibility 3. The event parameter is passed compatible.
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> & Lt;meta http-equiv= "Content-type" content= "text/html; charset=gb2312 "> </pead> <body> <div id=" Odiv "> Test text </div> </body> <script> v Ar ishostmethod = function (object, methodname) {var t = typeof Object[methodname]; return (t = = ' function ' | | t = = = ' object ') &&!! Object[methodname]) | | t = = = ' Unknown '; }; var addListener, RemoveListener, * test element */docel = document.documentelement; if (Ishostmethod (Docel, ' AddEventListener ')) {AddListener = function (element, EventName, handler) {Element.addeventli Stener (EventName, Handler, false); }; else if (Ishostmethod (Docel, ' attachevent ')) {AddListener = function (element, EventName, handler) {Element.attache Vent (' on ' + EveNtname, function () {Handler.call (element, window.event); }); }; else {AddListener = function (element, EventName, handler) {element[' on ' + eventName] = handler; }; } if (Ishostmethod (Docel, ' RemoveEventListener ')) {RemoveListener = function (element, EventName, handler) {ELEMENT.R Emoveeventlistener (EventName, Handler, false); }; else if (Ishostmethod (Docel, ' detachevent ')) {RemoveListener = function (element, EventName, handler) {Element.deta Chevent (' on ' + EventName, handler); }; else {RemoveListener = function (element, EventName, handler) {element[' on ' + eventName] = null; }; //Test usecase var o = document.getElementById (' Odiv '); AddListener (o, ' click ', Function (event) {This.style.backgroundColor = ' blue '; Alert ((Event.target | | event.srcelement). InnerHTML); }); </script> </ptml>
[Ctrl + A All SELECT Note: If the need to introduce external JS need to refresh to perform]
We just call the method this way:
Copy Code code as follows:
AddListener (o, ' click ', Function (event) {
This.style.backgroundColor = ' Blue ';
Alert ((Event.target | | event.srcelement). InnerHTML);
});
Visible ' Click ', this, event all achieve browser consistency. Is that all we got?
In fact, this is only the first step of long march. Because of the harmonious memory leak in IE browser, our event mechanism should be considered more complex than the above.
Take a look at one of our top amendments to this pointer code
Element.attachevent (' on ' + eventName, function () {
Handler.call (element, window.event);
});
Element--> handler--> element easily forms a circular reference. The memory is leaking under IE.
To disassociate a circular reference
The way to resolve memory leaks is to cut off the circular reference. That is, the reference to handler--> element is cut off. It is easy to think of the method, but also so far there are many libraries in the use of methods. is to point all handler to null when the window form unload.
The basic code is as follows
Code
Copy Code code 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, ' attachevent ')) {
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 with an array. The loop points to null all at once at Window.unload. The reference is severed from this.
It seems like a good way to do it. Good solution to the memory leak problem.
Avoid memory leaks
When we were just about to take a break. Another startling thing happened. Bfcache the page caching mechanism that is implemented by most mainstream browsers. A few of the terms that lead to cache invalidation are written
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 then 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 so frame (in this case, when the user navigates away from th e page, the content that's last loaded into the frames are what is cached)
The first is that our great unload will kill the page cache. The function of page caching is to. Each time we point forward the back button will read from the cache without having to request the server every time. So it's contradictory ...
We want both page caching. But we have to cut off the circular reference to the memory leak. But you can't use the Unload event ...
Finally, only the ultimate solution can be used. is to prohibit circular references
This program is also troublesome to introduce. But if you have seen the earliest event function of De great God. It is not difficult to understand. The following tasks need to be summed up.
1. Specify a unique UniqueID for each element.
2. Use a separate function to create the listener. However, this function does not refer to the element directly, avoiding circular references.
3. Created listening combined with independent UID and eventname
4. Trigger the wrapped event handle by attachevent.
After a series of analysis above. We got this relatively perfect event function in the end.
Copy Code code as follows:
(function (Global) {
Determine if there is a host property
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;
}
Get Unique ID
var Getuniqueid = (function () {
if (typeof Document.documentElement.uniqueID!== ' undefined ') {
return function (Element) {
return Element.uniqueid;
};
}
var uid = 0;
return function (Element) {
return Element.__uniqueid | | (Element.__uniqueid = ' uniqueid__ ' + uid++);
};
})();
Get/Set Element flags
var getelement, Setelement;
(function () {
var elements = {};
GetElement = function (UID) {
return ELEMENTS[UID];
};
Setelement = function (uid, Element) {
Elements[uid] = element;
};
})();
Create a monitor independently
function Createlistener (UID, handler) {
return {
Handler:handler,
Wrappedhandler:createwrappedhandler (UID, handler)
};
}
Event handle wrapper function
function Createwrappedhandler (UID, handler) {
return function (e) {
Handler.call (GetElement (UID), E | | window.event);
};
}
Distributing 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.documentelement, ' AddEventListener ', ' RemoveEventListener ') &&
Arehostmethods (window, ' AddEventListener ', ' RemoveEventListener ')),
Shoulduseattacheventdetachevent = (
Arehostmethods (document.documentelement, ' attachevent ', ' detachevent ') &&
Arehostmethods (window, ' attachevent ', ' 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 been developed to a more perfect level. But the total return is a place that we don't care about. Only marvel that IE and the world wide economy are too different for handling events.
The details of the omission
Although our hundreds of lines of code fixed a compatible event mechanism. But there are still areas to be perfected.
1. The capture phase is not supported because the MSHTML DOM does not support event mechanisms. So the third argument is missing.
2. Event handle trigger sequence. Most browsers are FIFO (first-in first out). And IE is going to a LIFO (LIFO). In fact, the DOM3 draft has already explained the specifies the order as FIFO.
No other details come in one by one.
The whole article to record their own ideas. So it's long-winded. But that relatively perfect event function is a little bit of a reference value, and hopefully it will help a little.
If you have good opinions and suggestions, please advise.
Code package download