Monday, December 11, 2006

In the "old-style" event registration model, you would typically register events by assigning functions to the onevent property of DOM elements:

elem.onclick = function() {alert("You clicked me");}

The problem with that approach is that you can only assign a single event handler function to any given event. All modern browsers support more advanced event registration mechanisms so you can attach multiple event listeners to any given event, though, as usual, those mechanisms vary across platforms. This recipe can be used to register and unregister event listeners in all modern browsers:

function addEventListener(instance, eventName, listener) {
    var listenerFn = listener;
    if (instance.addEventListener) {
        instance.addEventListener(eventName, listenerFn, false);
    } else if (instance.attachEvent) {
        listenerFn = function() {
            listener(window.event);
        }
        instance.attachEvent("on" + eventName, listenerFn);
    } else {
        throw new Error("Event registration not supported");
    }
    return {
        instance: instance,
        name: eventName,
        listener: listenerFn
    };
}
 
function removeEventListener(event) {
    var instance = event.instance;
    if (instance.removeEventListener) {
        instance.removeEventListener(event.name, event.listener, false);
    } else if (instance.detachEvent) {
        instance.detachEvent("on" + event.name, event.listener);
    }
}

The usage model for the functions above looks like this:

var elem = document.getElementById("elem");
var listener = addEventListener(elem, "click", function() {
    alert("You clicked me!");
});
removeEventListener(listener);

While those recipes function correctly in all major browsers, Internet Explorer has a lot of memory leak bugs that are exacerbated by event registration. While the details are a bit complex (subtle interactions between function closures and COM), we can augment the recipe above with a global event deregistration function that will remove memory leaks in most applications:

var __eventListeners = [];
 
 
function addEventListener(instance, eventName, listener) {
    var listenerFn = listener;
    if (instance.addEventListener) {
        instance.addEventListener(eventName, listenerFn, false);
    } else if (instance.attachEvent) {
        listenerFn = function() {
            listener(window.event);
        }
        instance.attachEvent("on" + eventName, listenerFn);
    } else {
        throw new Error("Event registration not supported");
    }
    var event = {
        instance: instance,
        name: eventName,
        listener: listenerFn
    };
    __eventListeners.push(event);
 
    return event;
}
 
function removeEventListener(event) {
    var instance = event.instance;
    if (instance.removeEventListener) {
        instance.removeEventListener(event.name, event.listener, false);
    } else if (instance.detachEvent) {
        instance.detachEvent("on" + event.name, event.listener);
    }
    for (var i = 0; i < __eventListeners.length; i++) {
        if (__eventListeners[i] == event) {
            __eventListeners.splice(i, 1);
            break;
        }
    }
 
}
 
function unregisterAllEvents() {
    while (__eventListeners.length > 0) {
        removeEventListener(__eventListeners[0]);
    }
}

The unregisterAllEvents function unregisters all events globally, which kills the references between the DOM objects and the listener functions, which generally prevents event registration memory leaks. To take advantage of the function, call it in the onunload handler for your page:

News Feed Source
Home Page:
Feed Title: Ajax Cookbook
Feed URL:
http://ajaxcookbook.org/feed/

Article
Title: Cross-Browser Event Handling and Memory Leaks
Link:
http://ajaxcookbook.org/event-handling-memory-leaks/
Author: ~Bret Taylor
Publication Date: 12/10/2006 9:55:27 PM

No comments: