понедельник, 18 декабря 2006 г.

Кросс-браузерный перехват событий Javascript и утечка памяти

В "старой школе" вы обычно регистрировали событие путем установки соответствия свойству onevent DOM элемента.

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

Проблема кроется в том, что вы можете назначить только одну функцию для указанного события. Но все современные браузеры поддерживают более продвинутые механизмы регистрации, т.о. вы можете регистрировать несколько функций на одно и то же событие. Но сам процесс регистрации таких событий обычно зависит от браузера. Следующий код позволит регистрировать/удалять события для всех современных браузеров:

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);
}
}

Пример использования вышеуказанных функций:

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


Несмотря на то, что этот рецепт работает во всех браузерах, Internet Explorer имеет в своем арсенале огромное количество багов, связанных с утечкой памяти и наше использование регистратора событий только обостряет ситуацию. Сама по себе проблема эта комплексная (тонкие взаимодействия между закрытиями функции и COM), но мы можем дополнить вышеуказанный рецепт при помощи глобальной функции де-регистрации, которая устранит проблему утечки памяти в большинстве случаев:

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]);
}
}


Функция unregisterAllEvents удаляет события глобально, удаляя связи между DOM объектом и слушающей функцией и тем самым решает проблему в большинстве случаев. Чтобы воспользоваться дополнительным преимуществом этой функции, вызывайте ее при возникновении события onunload:

<body onunload="unregisterAllEvents()">

Пример перехвата событий

Источник: http://ajaxcookbook.org/

4 комментария:

Анонимный комментирует...

То, что я искал!

Но по поводу

<body onunload="unregisterAllEvents()"&rt;

хочу сказать, что я лучше вызову эту функцию в самом скрипте:

window.onunload = function() { unregisterAllEvents() }

Анонимный комментирует...

"Старая" школа позволяет добавлять сколь угодно много обработчиков, если добавлять их с умом:

var oldHandler;

function NewHandler(){
if (oldHandler) {
oldHandler();
}

// далее код вашего нового обработчика
}

// сохраняем первоначальный обработчик
// например: onload
oldHandler = window.onload;
window.onload = newHandler;

Это все.

Анонимный комментирует...

"Мсье знает вкус в извращениях." (с)
То что ув. webtask нам порекомендовал подходит для проектов написанных полностью с нуля и одними руками. Люди далеко неглупые вводят новшества, от которых не стоит отказываться.

Unknown комментирует...

Для написания кроссбраузерных обработчиков удобно передать ссылку на вызвавший элемент, т.е. обозначить явно currentTarget там, где он не поддерживается.

function addEventListener( instance, eventName, listener) {
var listenerFn = listener;
if ( instance.addEventListener) {
instance.addEventListener( eventName, listenerFn, false);
} else if ( instance.attachEvent) {
listenerFn = function() {
instance.currentTarget = instance;
listener( instance);
}

instance.attachEvent("on" + eventName, listenerFn);
} else {
alert( "Event registration not supported");
}
return {
instance: instance,
name: eventName,
listener: listenerFn
}
}