Чи повинні усі події jquery бути пов'язані з $ (document)?


164

Звідки це походить

Коли я вперше дізнався jQuery, я зазвичай додав такі події:

$('.my-widget a').click(function() {
    $(this).toggleClass('active');
});

Дізнавшись більше про швидкість вибору та делегування подій, я прочитав у кількох місцях, що «делегація подій jQuery зробить ваш код швидше». Тому я почав писати код так:

$('.my-widget').on('click','a',function() {
    $(this).toggleClass('active');
});

Це також був рекомендований спосіб повторити поведінку застарілої події .live (). Що для мене важливо, оскільки багато моїх сайтів постійно динамічно додають / видаляють віджети. Наведене вище поводиться не так, як .live (), оскільки тільки елементи, додані до вже наявного контейнера '.my-widget', отримають таку поведінку. Якщо я динамічно додаю ще один блок html після запуску цього коду, ці елементи не зв'язатимуться з ними подіями. Подобається це:

setTimeout(function() {
    $('body').append('<div class="my-widget"><a>Click does nothing</a></div>');
}, 1000);


Чого я хочу досягти:

  1. стара поведінка .live () // означає приєднання подій до ще не існуючих елементів
  2. переваги .on ()
  3. найшвидша продуктивність для зв’язування подій
  4. Простий спосіб управління подіями

Зараз я додаю всі такі події:

$(document).on('click.my-widget-namespace', '.my-widget a', function() {
    $(this).toggleClass('active');
});

Що, здається, відповідає всім моїм цілям. (Так, у IE це чомусь повільніше, не знаю чому?) Це швидко, тому що лише окрема подія прив’язана до особливого елемента, а вторинний селектор оцінюється лише тоді, коли відбувається подія (будь ласка, виправте мене, якщо це неправильно тут). Простір імен є приголомшливим, оскільки полегшує перемикання слухача подій.

Моє рішення / питання

Тому я починаю думати, що події jQuery завжди повинні бути пов'язані з $ (document).
Чи є якась причина, чому ви б не хотіли цього робити?
Чи можна це вважати найкращою практикою? Якщо ні, то чому?

Якщо ви прочитали все це, дякую. Я вдячний за будь-який / всі відгуки / огляди.

Припущення:

  1. Використання jQuery, який підтримує .on()// принаймні версію 1.7
  2. Ви хочете, щоб подія була додана до динамічно доданого вмісту

Читання / приклади:

  1. http://24ways.org/2011/your-jquery-now-with-less-suck
  2. http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
  3. http://www.jasonbuckboyer.com/playground/speed/speed.html
  4. http://api.jquery.com/on/

Відповіді:


215

Ні - ви НЕ повинні прив'язувати до documentоб'єкта всі делеговані обробники подій . Це, мабуть, найгірший сценарій, який ви могли створити.

По-перше, делегування подій не завжди робить ваш код швидшим. У деяких випадках це вигідно, а в деяких випадках - ні. Ви повинні використовувати делегацію подій, коли вам дійсно потрібна делегація подій і коли ви отримаєте від неї користь. В іншому випадку слід прив’язати обробників подій безпосередньо до об'єктів, де відбувається подія, оскільки це, як правило, буде більш ефективним.

По-друге, НЕ слід пов'язувати всі делеговані події на рівні документа. Ось саме це і .live()було застарілим, оскільки це дуже неефективно, коли у вас багато подій, пов'язаних таким чином. Для обробки делегованих подій набагато ефективніше прив’язувати їх до найближчого батьківського, що не є динамічним.

По-третє, не всі події працюють або всі проблеми можна вирішити делегуванням. Наприклад, якщо ви хочете перехопити ключові події в керуванні входом і заблокувати недійсні ключі від введення керування входом, ви не можете цього зробити з делегованою обробкою події, оскільки до моменту подія перетворюється на делегований обробник, вона вже має обробляється вхідним керуванням, і надто пізно впливати на таку поведінку.

Ось такі випадки, коли необхідна або вигідна делегація подій:

  • Коли об’єкти, на які ви захоплюєте події, динамічно створюються / видаляються, і ви все одно хочете захоплювати події на них, не маючи явно перезавантажувати обробники подій кожного разу, коли ви створюєте нові.
  • Коли у вас багато об’єктів, всі хочуть точно такого ж обробника подій (де лотів принаймні сотні). У цьому випадку може бути ефективнішим під час налаштування зв’язати один делегований обробник подій, а не сотні або більше обробників прямих подій. Зауважте, делегована обробка подій завжди менш ефективна під час виконання, ніж прямі обробники подій.
  • Коли ви намагаєтеся захопити (на більш високому рівні у вашому документі) події, які відбуваються на будь-якому елементі документа.
  • Коли ваш дизайн явно використовує бульбашку подій та stopPropagation () для вирішення певної проблеми чи функції на вашій сторінці.

Щоб зрозуміти це ще трохи, потрібно зрозуміти, як працюють делеговані обробники подій jQuery. Коли ви телефонуєте щось подібне:

$("#myParent").on('click', 'button.actionButton', myFn);

Він встановлює загальний обробник подій jQuery на #myParentоб'єкт. Коли подія клацання перетворюється на цей делегований обробник подій, jQuery повинен пройти список делегованих обробників подій, приєднаних до цього об'єкта, і побачити, чи відповідає вихідний елемент події будь-якому із селекторів делегованих обробників подій.

Оскільки селектори можуть бути досить задіяними, це означає, що jQuery повинен проаналізувати кожен селектор, а потім порівняти його з характеристиками вихідної цілі події, щоб побачити, чи відповідає він кожному селектору. Це не дешева операція. Не зайвим буде, якщо є лише один із них, але якщо ви поставите всіх своїх селекторів на об'єкт документа, а для порівняння з кожною повальною подією було сотні селекторів, це може серйозно почати сприймати ефекти обробки подій.

З цієї причини ви хочете налаштувати делеговані обробники подій, щоб делегований обробник подій був максимально наближений до цільового об'єкта. Це означає, що через кожний делегований обробник подій буде перебігати менше подій, тим самим покращуючи ефективність. Розміщення всіх делегованих подій на об’єкт документа - це найгірша можлива ефективність, оскільки всі події, що переносяться, мають пройти через всі делеговані обробники подій та отримати оцінку щодо всіх можливих делегованих селекторів подій. Ось саме .live()це і є застарілим, оскільки саме це .live()зробило і виявилося дуже неефективним.


Отже, для досягнення оптимізованої продуктивності:

  1. Використовуйте делеговану обробку подій лише тоді, коли вона фактично надає необхідну вам функцію або підвищує продуктивність. Не завжди використовуйте його, тому що це легко, тому що, коли вам це насправді не потрібно. Він фактично виконує гірший час відправки події, ніж пряме прив'язування події.
  2. Приєднайте делегованих обробників подій до найближчого батьків до джерела події, наскільки це можливо. Якщо ви використовуєте делеговану обробку подій, оскільки у вас є динамічні елементи, для яких ви хочете фіксувати події, то виберіть найближчий батьківський, який сам по собі не є динамічним.
  3. Використовуйте прості в оцінці селектори для делегованих обробників подій. Якщо ви стежили за тим, як працює делегована обробка подій, ви зрозумієте, що делегований обробник подій потрібно порівнювати з великою кількістю об'єктів багато разів, тому вибір максимально ефективного селектора або додавання простих класів до ваших об'єктів, щоб прості селектори були використані підвищити ефективність делегованої обробки подій.

2
Дивовижно. Дякую за швидкий та детальний опис. Це має сенс, що час відправки події збільшиться за допомогою .on (), але я думаю, що я все ще намагаюся вирішити між збільшеним часом відправлення події проти початковою командою обробки сторінки. Я, як правило, за скорочений час початкової сторінки. Наприклад, якщо я маю 200 елементів на сторінці (і більше, ніж трапляються події), то це в 100 разів дорожче при початковому завантаженні (оскільки до неї потрібно додати 100 слухачів подій), а не якщо я додаю на слухачів сингулярного події посилання на
Бак

Також хотів сказати, що ви мене переконали - Не прив'язуйте всі події до $ (документ). Прив’яжіться до найближчого батьків, що не змінюються. Напевно, мені все ж доведеться вирішувати в кожному конкретному випадку, коли делегування події краще, ніж безпосередньо прив’язування події до елемента. І коли мені потрібні події, прив’язані до динамічного контенту (у якого немає батьків, що змінюється), я думаю, я продовжуватиму робити те, що @Vega рекомендує нижче, і просто "зв'язати обробник (-ів) після того, як вміст вставиться в ( ) DOM ", використовуючи контекстний параметр jQuery в моєму селекторі.
Бак

5
@ user1394692 - якщо у вас багато майже однакових елементів, можливо, має сенс використовувати делеговані обробники подій для них. Я це кажу у своїй відповіді. Якби у мене була гігантська таблиця з 500 рядків і була однакова кнопка в кожному рядку певного стовпця, я використовував би один делегований обробник подій на столі, щоб обслуговувати всі кнопки. Але якби у мене було 200 елементів, і всі вони потребували власного унікального обробника подій, установка 200 делегованих обробників подій не є швидшою, ніж установка 200 безпосередньо пов'язаних обробників подій, однак делегована обробка подій може бути набагато повільнішою після виконання події.
jfriend00

Ти досконалий. Повністю згоден! Я розумію, що це найгірше, що я можу зробити - $(document).on('click', '[myCustomAttr]', function () { ... });?
Артем Фітіскін

Використання pjax / turbolinks створює цікаву проблему, оскільки всі елементи динамічно створюються / видаляються (крім завантаження першої сторінки). Тож чи краще прив’язати всі події один раз до document, або прив’язати до більш конкретних виділень page:loadі відв'язати ці події page:after-remove? Хммм
Дом Крісті

9

Делегування подій - це техніка запису ваших обробників до того, як елемент дійсно існує в DOM. У цього методу є свої недоліки і його слід застосовувати лише за наявності таких вимог.

Коли слід використовувати делегування подій?

  1. Коли ви прив'язуєте загальний обробник для більшої кількості елементів, які потребують однакової функціональності. (Наприклад: наведення курсора на рядок)
    • У прикладі, якби вам довелося зв'язати всі рядки за допомогою прямого прив'язки, ви створили б n обробника для n рядків у цій таблиці. Використовуючи метод делегування, ви зможете обробити всіх, хто перебуває в одному простому обробнику.
  2. Коли ви частіше додаєте динамічний вміст у DOM (напр .: додавання / видалення рядків із таблиці)

Чому не слід використовувати делегацію подій?

  1. Делегування події відбувається повільніше порівняно з прив'язкою події безпосередньо до елемента.
    • Він порівнює цільовий селектор на кожен пузир, у який потрапив, порівняння буде таким же дорогим, як і складним.
  2. Ніякого контролю над бульбашкою події, поки він не потрапить на елемент, до якого він пов'язаний.

PS: Навіть для динамічного вмісту вам не потрібно використовувати метод делегування подій, якщо ви прив'язуєте обробник після того, як вміст вставляється в DOM. (Якщо динамічний вміст буде додано не часто видаляється / повторно додається)


0

Мабуть, зараз вже рекомендується делегування заходів. принаймні для ванілі js.

https://gomakethings.com/why-event-delegation-is-a-better-way-to-listen-for-events-in-vanilla-js/

"Веб-продуктивність # Виглядає, що прослуховування кожного клацання в документі буде поганим для продуктивності, але насправді воно є більш ефективним, ніж наявність у багатьох користувачів слухачів подій."


0

Я хотів би додати деякі зауваження та контраргументи до відповіді jfriend00. (в основному це лише моя думка, заснована на моєму відчутті кишечника)

Ні - НЕ слід прив'язувати всі делеговані обробники подій до об'єкта документа. Це, мабуть, найгірший сценарій, який ви могли створити.

По-перше, делегування подій не завжди робить ваш код швидшим. У деяких випадках це вигідно, а в деяких випадках - ні. Ви повинні використовувати делегацію подій, коли вам дійсно потрібна делегація подій і коли ви отримаєте від неї користь. В іншому випадку слід прив’язати обробників подій безпосередньо до об'єктів, де відбувається подія, оскільки це, як правило, буде більш ефективним.

Хоча це правда, що продуктивність може бути дещо кращою, якщо ви збираєтесь лише зареєструватися та подіяти для одного елемента, я вважаю, що це не відповідає перевагам масштабування, який приносить делегація. Я також вважаю, що браузери (це буде) обробляють це все ефективніше і ефективніше, хоча я не маю доказів цього. На мою думку, делегація подій - це шлях!

По-друге, НЕ слід пов'язувати всі делеговані події на рівні документа. Саме тому .live () був застарілим, оскільки це дуже неефективно, коли у вас багато подій, пов'язаних таким чином. Для обробки делегованих подій набагато ефективніше прив’язувати їх до найближчого батьківського, що не є динамічним.

Я з цим погоджуюся. Якщо ви на 100% впевнені, що подія відбудеться лише всередині контейнера, є сенс прив’язати подію до цього контейнера, але я все одно заперечуватимуть проти подій прив'язки безпосередньо до запускаючого елемента.

По-третє, не всі події працюють або всі проблеми можна вирішити делегуванням. Наприклад, якщо ви хочете перехопити ключові події в керуванні входом і заблокувати недійсні ключі від введення керування входом, ви не можете цього зробити з делегованою обробкою події, оскільки до моменту подія перетворюється на делегований обробник, вона вже має обробляється вхідним керуванням, і надто пізно впливати на таку поведінку.

Це просто неправда. Перегляньте цей кодPen: https://codepen.io/pwkip/pen/jObGmjq

document.addEventListener('keypress', (e) => {
    e.preventDefault();
});

Це ілюструє, як можна запобігти введенню користувача, зареєструвавши події натискання клавіші на документі.

Ось такі випадки, коли необхідна або вигідна делегація подій:

Коли об’єкти, на які ви захоплюєте події, динамічно створюються / видаляються, і ви все одно хочете захоплювати події на них, не маючи явно перезавантажувати обробники подій кожного разу, коли ви створюєте нові. Коли у вас багато об’єктів, всі хочуть точно такого ж обробника подій (де лотів принаймні сотні). У цьому випадку може бути ефективнішим під час налаштування зв’язати один делегований обробник подій, а не сотні або більше обробників прямих подій. Зауважте, делегована обробка подій завжди менш ефективна під час виконання, ніж прямі обробники подій.

Я хочу відповісти цією цитатою з https://ehsangazar.com/optimizing-javascript-event-listeners-for-performance-e28406ad406c

Event delegation promotes binding as few DOM event handlers as possible, since each event handler requires memory. For example, let’s say that we have an HTML unordered list we want to bind event handlers to. Instead of binding a click event handler for each list item (which may be hundreds for all we know), we bind one click handler to the parent unordered list itself.

Крім того, гугл для performance cost of event delegation googleповернення більше результатів на користь делегації подій.

Коли ви намагаєтеся захопити (на більш високому рівні у вашому документі) події, які відбуваються на будь-якому елементі документа. Коли ваш дизайн явно використовує бульбашку подій та stopPropagation () для вирішення певної проблеми чи функції на вашій сторінці. Щоб зрозуміти це ще трохи, потрібно зрозуміти, як працюють делеговані обробники подій jQuery. Коли ви телефонуєте щось подібне:

$ ("# myParent"). on ('натиснути', 'button.actionButton', myFn); Він встановлює загальний обробник подій jQuery на об’єкті #myParent. Коли подія клацання перетворюється на цей делегований обробник подій, jQuery повинен пройти список делегованих обробників подій, приєднаних до цього об'єкта, і побачити, чи відповідає вихідний елемент події будь-якому із селекторів делегованих обробників подій.

Оскільки селектори можуть бути досить задіяними, це означає, що jQuery повинен проаналізувати кожен селектор, а потім порівняти його з характеристиками вихідної цілі події, щоб побачити, чи відповідає він кожному селектору. Це не дешева операція. Не важливо, якщо є лише одна з них, але якщо ви поставите всіх своїх селекторів на об'єкт документа, і для їх порівняння з кожною повальною подією було сотні селекторів, це може серйозно почати сприймати ефективність обробки подій.

З цієї причини ви хочете налаштувати делеговані обробники подій, щоб делегований обробник подій був наближений до цільового об'єкта, наскільки це практично. Це означає, що через кожний делегований обробник подій буде проводитись менше подій, тим самим покращуючи ефективність. Розміщення всіх делегованих подій на об’єкт документа - це найгірша можлива ефективність, оскільки всі події, що переносяться, мають пройти через всі делеговані обробники подій та отримати оцінку щодо всіх можливих делегованих селекторів подій. Саме тому .live () застаріло, оскільки саме це зробив .live () і виявилося дуже неефективним.

Де це документально зафіксовано? Якщо це правда, то, схоже, jQuery поводиться з делегуванням дуже неефективно, і тоді мої контр-аргументи слід застосовувати лише до ванільної JS.

Все-таки: я хотів би знайти офіційне джерело, яке б підтверджувало це твердження.

:: EDIT ::

Схоже, jQuery справді робить надзвичайно неефективним способом (тому, що вони підтримують IE8) https://api.jquery.com/on/#event-performance

Тож більшість моїх аргументів справедливі лише для ванільної JS та сучасних браузерів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.