Skip to content

Instantly share code, notes, and snippets.

@eduardocereto
Created May 4, 2011 17:45
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save eduardocereto/955642 to your computer and use it in GitHub Desktop.
Save eduardocereto/955642 to your computer and use it in GitHub Desktop.
a cross-browser implementation of addEventListener/AttachEvent without external dependencies
/**
* Cross Browser helper to addEventListener.
*
* @param {HTMLElement} obj The Element to attach event to.
* @param {string} evt The event that will trigger the binded function.
* @param {function(event)} fnc The function to bind to the element.
* @return {boolean} true if it was successfuly binded.
*/
var cb_addEventListener = function(obj, evt, fnc) {
// W3C model
if (obj.addEventListener) {
obj.addEventListener(evt, fnc, false);
return true;
}
// Microsoft model
else if (obj.attachEvent) {
return obj.attachEvent('on' + evt, fnc);
}
// Browser don't support W3C or MSFT model, go on with traditional
else {
evt = 'on'+evt;
if(typeof obj[evt] === 'function'){
// Object already has a function on traditional
// Let's wrap it with our own function inside another function
fnc = (function(f1,f2){
return function(){
f1.apply(this,arguments);
f2.apply(this,arguments);
}
})(obj[evt], fnc);
}
obj[evt] = fnc;
return true;
}
return false;
};
@pg2800
Copy link

pg2800 commented Jan 12, 2014

ORIGINAL COMMENT: 12-January-2014.
UPDATED 13-January-2014.

I was intrigued with your implementation and I believe I added some best practices to the code in general and also added some improvements to the "traditional" way.

All three implementations of the addEvent custom method below (meaning: with or without any of the addEventListener or attachEvent -- forcing the browser to test all three) worked for:
CHROME: Version 32.0.1700.72 m
FIREFOX: 26.0
EXPLORER: Version 10.0.9200.16750

Needless to say; I didn't examine all possible scenarios in my test cases, only a few...
Let me know what you think.

(function(){
  // I test for features at the beginning of the declaration instead of everytime that we have to add an event.
  if(document.addEventListener) {
    window.addEvent = function (elem, type, handler, useCapture){
      elem.addEventListener(type, handler, !!useCapture);
      return handler; // for removal purposes
    }
    window.removeEvent = function (elem, type, handler, useCapture){
      elem.removeEventListener(type, handler, !!useCapture);
      return true;
    }
  } 
  else if (document.attachEvent) {
    window.addEvent = function (elem, type, handler) {
      type = "on" + type;
      // Bounded the element as the context 
      // Because the attachEvent uses the window object to add the event and we don't want to polute it.
      var boundedHandler = function() {
        return handler.apply(elem, arguments);
      };
      elem.attachEvent(type, boundedHandler);
      return boundedHandler; // for removal purposes
    }
    window.removeEvent = function(elem, type, handler){
      type = "on" + type;
      elem.detachEvent(type, handler);
      return true;
    }
  } 
  else { // FALLBACK ( I did some test for both your code and mine, the tests are at the bottom. )
    // I removed wrapping from your implementation and added closures and memoization.
    // Browser don't support W3C or MSFT model, go on with traditional
    window.addEvent = function(elem, type, handler){
      type = "on" + type;
      // Applying some memoization to save multiple handlers
      elem.memoize = elem.memoize || {};
      // Just in case we haven't memoize the event type yet.
      // This code will be runned just one time.
      if(!elem.memoize[type]){
        elem.memoize[type] = { counter: 1 };
        elem[type] = function(){
          for(key in nameSpace){
            if(nameSpace.hasOwnProperty(key)){
              if(typeof nameSpace[key] == "function"){
                nameSpace[key].apply(this, arguments);
              };
            };
          };
        };
      };
      // Thanks to hoisting we can point to nameSpace variable above.
      // Thanks to closures we are going to be able to access its value when the event is triggered.
      // I used closures for the nameSpace because it improved 44% in performance in my laptop.
      var nameSpace = elem.memoize[type], id = nameSpace.counter++;
      nameSpace[id] = handler;
      // I return the id for us to be able to remove a specific function binded to the event.
      return id;
    };
    window.removeEvent = function(elem, type, handlerID){
      type = "on" + type;
      // I remove the handler with the id
      if(elem.memoize && elem.memoize[type] && elem.memoize[type][handlerID]) elem.memoize[type][handlerID] = undefined;
      return true;
    };

  };
})();

The first two (with addEventListener or attachEvent) run as the original ones, didn't notice any differences. But for the "traditional way":

My original test was 150k repetitions of adding an empty function to the element's event and then run the event. But as you wrap the handlers onto each other; javascript sends the next error: "Maximum call stack size exceeded" which is only natural.

Then I tested for the maximum stack size allowed which was 7816 ( I made that my test size), the results of adding 7816 empty functions to the same type of event of the same element and then executing the event was:

Your code: minimum = 19ms, maximum = 33ms, average = 30ms.
My code: minimum = 20ms, maximum = 37ms, average = 27ms.

There is obviously not an improvement on performance whatsoever, but we can now delete specific handlers and also we have room for more handlers, and we can use this to standarize our code with the same function to add and to remove events, so we don't have to worry about X-browser considerations.

If we were to have very little to none memory available, I would definitely go with your implementation.

--> Tests done with a Sony vaio 8GB RAM, core i7 second generation.

i.e.

var div = getElementById("divID"); // returns a div element
var handler = addEvent(div, "click", function(){
 /* do something */
}, false);

/* more code */

console.log( removeEvent(div, "click", handler) );

@cyrildewit
Copy link

Im just curious, why are using if else statemants to make a crossbrowser addeventlistener function if everbody else on the internet is saying that this is bad practise. Can you explain a little bit about this descision?

@jcubic
Copy link

jcubic commented Feb 20, 2017

All modern browsers including IE9, supports addEventListener see http://caniuse.com/#feat=addeventlistener

@aamirafridi
Copy link

@jcubic I can see only IE11
image

@jcubic
Copy link

jcubic commented Jul 10, 2018

@aamirafridi you need to expand all to see IE10 and IE9. It's measleading because it never show prev versions of IE

@luislobo14rap
Copy link

I liked it there, I didn't test it because I don't need it, but reviewing things for old browsers is very interesting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment