Оптимизация JavaScript часть 3: Подписка на события

Это третья часть учебника по оптимизации JavaScript, и сегодня я хочу поговорить о событиях. Простите за длительный перерыв между статьями, надеюсь, оставшиеся части будут публиковаться регулярнее.

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

Это обычная задача в веб-разработке. И первая вещь, которую знает каждый, — это разный синтаксис подписки на события в Internet Explorer и Firefox: первый использует element.attachEvent, второй — element.addEventListeners. Поэтому Вам нужно добавлять проверки, какой браузер используется в каждом конкретном случае. В наиболее популярной библиотеке Prototype это уже стандартизировано, и Вы всегда можете использовать Event.observe. Давайте немного потестируем.

Я начну с подписки на события через определение браузера вручную:

// Attaching events
for (var i = items.length; i--; ) {
    if (items[i].addEventListener) {

        items[i].addEventListener('click', e_onclick, false);

    } else if (items[i].attachEvent) {

        items[i].attachEvent('onclick', e_onclick);
    }

}
// Detaching events
for (var i = items.length; i--; ) {
    if (items[i].removeEventListener) {

        items[i].removeEventListener('click', e_onclick, false);

    } else if (items[i].detachEvent) {

        items[i].detachEvent('onclick', e_onclick);
    }

}

Этот подход показал лучшее время: 188 и 203 ms в Internet Explorer 6 и 7, 125 и 141 ms в Firefox 1.5 and 2.0, и 63 ms в Opera 9, но есть одна проблема — утечки памяти: Internet Explorer обычно забывает почистить память, используемую обработчиками событий, когда вы переходите на другую страницу. Поэтому все JavaScript-фреймворки, реализующие функции подписки/отписывания от событий, предоставляют возможность автоматического удаления обработчиков при переходе на другую страницу. Давайте потестируем их.

Код для библиотеки Prototype:

// Attaching events
for (var i = items.length; i--; ) {
    Event.observe(items[i], 'click', e_onclick, false);

}
// Detaching events
for (var i = items.length; i--; ) {
    Event.stopObserving(items[i], 'click', e_onclick, false);

}

Это очень медленно, 6453 ms в Internet Explorer 6, и похоже на то, что все еще есть какие-то утечки памяти (становится все медленнее и медленнее со временем), и 365 – 653 ms в других браузерах.

Существует множество проблем и решений, связанных с подпиской на события (взгляните только на Advanced event registration models). Не так давно даже прошло соревнование PPK's addEvent() Recoding Contest (но не вздумайте использовать решение победителя в своих приложениях, оно крайне неэффективно и приводит к утечкам памяти).

Вместо него я решил протестировать невероятное и дерзкое решение от Dean Edwards, которое даже не использует методы addeventListener/attachEvent, но является полностью кросс-браузерным!

Использованный код:

// Attaching events
for (var i = items.length; i--; ) {

    addEvent(items[i], 'click', e_onclick);
}
// Detaching events

for (var i = items.length; i--; ) {
    removeEvent(items[i], 'click', e_onclick);

}

Результаты настолько же впечатляющие, как и при подписке на события вручную, этот подход невероятно быстр для использования в реальных приложениях. Безусловно, вы скажете: «Стоп, о чем ты говоришь? Я не хочу использовать дополнительные методы, ведь я уже использую Prototype в своем коде!» Остыньте, вам не нужно этого делать, просто скачайте библиотеку Low Pro (текущая версия — 0.4, не забывайте обновляться). Разработчики Ruby on Rails могут воспользоваться плагином UJS Rails Plugin в своих приложениях для улучшения производительности — он включает и оптимизации Low Pro.

NoMethodIE 6IE 7FF 1.5FF 2.0Opera 9
1manually20318812514163
2prototype.js6453653547469365
3addEvent7833449414162

Вы можете посмотреть тест и получить собственные результаты производительности здесь.

Выводы

  • Всегда используйте Low Pro с Prototype.js (конечно, если ваше приложение содержит продвинутую логику на стороне клиента).
  • Подписывайтесь на события вручную, если нужно выжать максимальную скорость из вашего приложения (например, когда вы манипулируете сотнями элементов). Но не забывайте об утечках памяти!
  • Избегайте подписки на события везде, где это возможно (например, используйте CSS селектор :hover вместо события onmouseover).
  • Будьте начеку, возможно завтра кто-то опубликует другие подсказки по оптимизации производительности.

Читать дальше

Автор: Штефлюк Дмитрий

Все комментарии