Javascript browser compatibility Event Processing Mechanism

Source: Internet
Author: User
Tags define function
Javascript browser compatibility is a headache and complicated task. Therefore, it is necessary to write a cross-browser event processing function during development. This article introduces the javascript browser compatibility event processing mechanism to programmers. 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.

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.

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

Function loadHandler () {alert ('the page is loaded! ');} Window. attachEvent ('onload', loadHandler); // Add the 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.

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

var addListener, removeListener, /* test element */ docEl = document.documentElement; // 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 `attachEvent` exists on test element, 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 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.

// 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

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.

var addListener, docEl = document.documentElement; if (isHostMethod(docEl, 'addEventListener')) { /* ... */ } else if (isHostMethod(docEl, 'attachEvent')) { /* ... */ } 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.

// 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.

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 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:

Event Test UseCase 
     

Test text

Script var 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.doc umentElement; if (isHostMethod (docEl, 'addeventlistener ') {addListener = function (element, eventName, handler) {element. addEventListener (eventName, handler, false) ;};} else if (isHostMethod (docEl, 'attachevent') {addListener = function (element, eventName, handler) {element. attachEvent ('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. removeEventListener (eventName, handler, false) ;};} else if (isHostMethod (docEl, 'detachevent') {removeListener = function (element, eventName, handler) {element. detachEvent ('on' + eventName, handler) ;};} else {removeListener = function (element, eventName, handler) {element ['on' + eventName] = null ;}// Test UseCase var o = document. getElementById ('op'); addListener (o, 'click', function (event) {this. style. backgroundColor = 'Blue '; alert((event.tar get | event. srcElement ). innerHTML) ;}); script

We only need to call the method like this:

addListener(o, 'click', function(event) { this.style.backgroundColor = 'blue'; alert((event.target || 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:

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 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 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.

(Function (global) {// determines whether the 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;} // 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 ++) ;};} (); // obtain/set the element flag var getElement, setElement; (function () {var elements ={}; getElement = function (uid) {return elements [uid] ;}; setElement = function (uid, element) {elements [uid] = element ;};})();// Independent listening function createListener (uid, handler) {return {handler: handler, wrappedHandler: createWrappedHandler (uid, handler)};} // The Event handle packaging function createWrappedHandler (uid, handler) {return function (e) {handler. call (getElement (uid), e | window. event) ;}}// dispatch event function createDispatcher (uid, eventName) {return function (e) {if (handlers [uid] & handlers [uid] [eventName]) {var handlersFo REvent = handlers [uid] [eventName]; for (var I = 0, len = handlersForEvent. length; I <len; I ++) {handlersForEvent [I]. call (this, e | window. event) ;}}}// main function var addListener, removeListener, listener = (areHostMethods(document.doc umentElement, 'addeventlistener ', 'removeeventlistener') & areHostMethods (window, 'addeventlistener ', 'removeeventlistener'), shouldUs Response = (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 (elem Ent, 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 [ui D] [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.

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.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.