Adding and removing listeners-part-2

Benchmark created by David Mark on


Basic DOM operation: adding and a listener to an element and then removing

As an added challenge, the - this - object must be set for the listener. This gets really ugly in jQuery.

This is an alternate version of this test:-

...adding legacy IE support (e.g. IE 5.5 - IE 8) and without introducing leaky circular references involving host objects.

Note that all jQuery-based scripts leak memory in legacy IE browsers until the page is unloaded (e.g. by navigation). This is a bear for sites/apps that never navigate (e.g. single page apps).

This version adds a small bit of code, which can be safely hidden in IE conditional comments (lt IE 9) as the only known browsers to feature attachEvent without addEventListener are the old MSHTML-based browsers. If others exist, they are good candidates for degradation.

Preparation HTML

<script src="//"></script>
 * Context here is an HTML5 document
 * Appropriate build for this context would exclude XHTML support
 * Next line asserts document will create an HTML DOM
 * There are virtually no documents on the Web that create an XHTML DOM
var API = { disableXmlParseMode:true };
<script src="//"></script>
  // For My Library test
  var attachListener = API.attachListener;
  var detachListener = API.detachListener;
  // For cross-browser test
  // Degrades in browsers lacking native bind method (many)
  // Alternatively, use (adds back most of non-IE)
  // No frames - one document only
  var attachEventListener, removeAttachedEventListener;
  var elHtml = document.documentElement;
  if (elHtml) { // Need HTML element for feature detection
    // NOTE: Should use isHostMethod for host method detection
    if (elHtml.addEventListener && Function.prototype.bind) {
      attachEventListener = function(el, eventType, fn, thisObject) {
        var boundFunction = fn.bind(thisObject || el);
        el.addEventListener(eventType, boundFunction, false);
        return boundFunction; // NOTE: returns bound function for removal
      removeAttachedEventListener = function(el, eventType, fn) {
        el.removeEventListener(eventType, fn);
    } else if (elHtml.attachEvent && {
      // Degrades under IE 5.5
      // NOTE: As written for test, all variables are global.
      // Wrap the whole thing in a "module" function and leaks will
      // be reintroduced. The "theseObjects" variable must be global
      // or a property of a global object (e.g. a "namespace" object).
      // Stores references to objects used for - this - object in listeners
      var theseObjects = [];
      // Index for theseObjects array, bumped on each attachment
      var theseObjectsIndex = 0;
      attachEventListener = function(el, eventType, fn, thisObject) {
        var thisObjectIndex = theseObjectsIndex++;
        // Store reference to object used for - this - in listener
        theseObjects[thisObjectIndex] = thisObject || el;
        var boundFunction = function() {
[thisObjectIndex], window.event);
        el.attachEvent('on' + eventType, boundFunction);
        // Discard unneeded references
        // Prevents circular references with host objects (e.g. the element)
        // Prevents IE leaks related to such circular references
        // No unload event cleanup required
        thisObject = el = null;
        return boundFunction;
      removeAttachedEventListener = function(el, eventType, fn) {
        el.detachEvent('on' + eventType, fn);
  // For all tests
  var el1 = document.createElement('div');
  var el2 = document.createElement('div');
  var el3 = document.createElement('div');
  var el4 = document.createElement('div');
  var listenerFunction = function() {
    var x = 1;
  var thisObject = {};

Test runner

Ready to run.

Testing in
My Library
attachListener(el1, 'click', listenerFunction, thisObject);
detachListener(el1, 'click', listenerFunction);
My Library (OO)
E(el2).on('click', listenerFunction, thisObject).off('click', listenerFunction);
jQuery(el3).bind('click', jQuery.proxy(listenerFunction, thisObject)).unbind('click', jQuery.proxy(listenerFunction, thisObject));

// Apparently the only way to do this in one chain :(
// Breaking it in two or three wouldn't be in the spirit of jQuery.
if (attachEventListener) { // Conditionally defined
  removeAttachedEventListener(el4, 'click', attachEventListener(el4, 'click', listenerFunction, thisObject));
} else {
  throw new Error('Abort due to degraded browser');


You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by David Mark on