Должны ли все события 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объект. Когда событие click вспыхивает до этого обработчика делегированных событий, jQuery должен просмотреть список обработчиков делегированных событий, прикрепленных к этому объекту, и посмотреть, соответствует ли исходный элемент для события любому из селекторов в обработчиках делегированных событий.

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

По этой причине вы хотите настроить делегированные обработчики событий так, чтобы делегированный обработчик событий был как можно ближе к целевому объекту. Это означает, что через каждый делегированный обработчик событий будет проходить меньше событий, что повышает производительность. Помещение всех делегированных событий в объект документа является наихудшей возможной производительностью, поскольку все всплывающие события должны проходить через все делегированные обработчики событий и оцениваться для всех возможных делегированных селекторов событий. Именно поэтому .live()не рекомендуется, потому что это то, что .live()сделал, и это оказалось очень неэффективным.


Итак, для достижения оптимизированной производительности:

  1. Используйте делегированную обработку событий только тогда, когда она действительно предоставляет нужную вам функцию или повышает производительность. Не всегда используйте это, потому что это легко, потому что когда вам это не нужно. На самом деле он работает хуже во время отправки события, чем прямая привязка события.
  2. Присоедините делегированные обработчики событий к ближайшему родителю к источнику события, насколько это возможно. Если вы используете делегированную обработку событий, поскольку у вас есть динамические элементы, для которых вы хотите захватывать события, выберите ближайшего родителя, который сам по себе не является динамическим.
  3. Используйте простые в оценке селекторы для делегированных обработчиков событий. Если вы следили за тем, как работает делегированная обработка событий, вы поймете, что делегированный обработчик событий нужно сравнивать с большим количеством объектов много раз, поэтому выбор максимально эффективного селектора или добавление простых классов к вашим объектам, чтобы можно было использовать более простые селекторы повысить производительность делегированной обработки событий.
jfriend00
источник
2
Потрясающие. Спасибо за быстрое и подробное описание. Это имеет смысл, что время отправки события будет увеличиваться при использовании .on (), но я все еще пытаюсь выбрать между увеличенным временем отправки события и начальной командой обработки страницы. Я вообще за уменьшенное время начальной страницы. Например, если у меня есть 200 элементов на странице (и больше по мере того, как происходят события), это примерно в 100 раз дороже при начальной загрузке (поскольку необходимо добавить 100 прослушивателей событий), чем если бы я добавлял особый прослушиватель событий в ссылка на
Buck
Также хотел сказать, что вы меня убедили - не привязывайте все события к $ (документ). Привязать к ближайшему неизменному родителю, насколько это возможно. Думаю, мне все равно придется решать в каждом конкретном случае, когда делегирование события лучше, чем прямая привязка события к элементу. И когда мне нужны события, привязанные к динамическому контенту (у которого нет неизменяемого родителя), я думаю, что я буду продолжать делать то, что @Vega рекомендует ниже, и просто «связать обработчик (и) после того, как содержимое будет вставлено в ( ) DOM ", используя контекстный параметр jQuery в моем селекторе.
Бак
5
@ user1394692 - если у вас много почти идентичных элементов, то может иметь смысл использовать для них делегированные обработчики событий. Я говорю это в своем ответе. Если бы у меня была гигантская таблица из 500 строк и одна и та же кнопка в каждой строке определенного столбца, я бы использовал один делегированный обработчик событий в таблице для обслуживания всех кнопок. Но если бы у меня было 200 элементов, и все они нуждались в собственном уникальном обработчике событий, установка 200 делегированных обработчиков событий не быстрее, чем установка 200 напрямую связанных обработчиков событий, но делегированная обработка событий может быть намного медленнее при выполнении события.
jfriend00
Вы идеальны. Полностью согласен! Я понимаю, что это худшее, что я могу сделать $(document).on('click', '[myCustomAttr]', function () { ... });?
Артем Фитискин
Использование pjax / turbolinks создает интересную проблему, поскольку все элементы динамически создаются / удаляются (кроме загрузки первой страницы). Так что лучше , чтобы связать все события , как только к document, или связываются с более конкретными выборов на page:loadи UNBIND этих событий на 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/

«Производительность в Интернете. Такое ощущение, что прослушивание каждого клика в документе может сказаться на производительности, но на самом деле это более производительно, чем иметь кучу слушателей событий по отдельным элементам».

Reincha
источник
0

Я хотел бы добавить некоторые замечания и контраргументы к ответу jfriend00. (в основном только мои мнения, основанные на моем чувстве кишки)

Нет - вам НЕ следует связывать все делегированные обработчики событий с объектом документа. Это, вероятно, худший сценарий, который вы могли бы создать.

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

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

Во-вторых, вы НЕ должны связывать все делегированные события на уровне документа. Именно поэтому .live () устарела, потому что это очень неэффективно, когда у вас много событий, связанных таким образом. Для делегированной обработки событий НАМНОГО более эффективно связать их с ближайшим родителем, который не является динамическим.

Я вроде согласен с этим. Если вы на 100% уверены, что событие произойдет только внутри контейнера, имеет смысл связать событие с этим контейнером, но я бы все же поспорил с тем, чтобы связывать события с инициирующим элементом напрямую.

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

Это просто неправда. Пожалуйста, смотрите этот codePen: 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.

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

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

$ ("# myParent"). on ('click', 'button.actionButton', myFn); Он устанавливает универсальный обработчик событий jQuery на объект #myParent. Когда событие click вспыхивает до этого обработчика делегированных событий, jQuery должен пройти через список обработчиков делегированных событий, прикрепленных к этому объекту, и посмотреть, соответствует ли исходный элемент для события любому из селекторов в обработчиках делегированных событий.

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

По этой причине вы хотите настроить делегированные обработчики событий так, чтобы делегированный обработчик событий был как можно ближе к целевому объекту. Это означает, что через каждый делегированный обработчик событий будет проходить меньше событий, что повышает производительность. Помещение всех делегированных событий в объект документа является наихудшей возможной производительностью, поскольку все всплывающие события должны проходить через все делегированные обработчики событий и оцениваться для всех возможных делегированных селекторов событий. Именно поэтому .live () устарела, потому что именно это .live () сделала, и это оказалось очень неэффективным.

Где это задокументировано? Если это так, то jQuery, похоже, обрабатывает делегирование очень неэффективным образом, и тогда мои контраргументы должны применяться только к vanilla JS.

Тем не менее: я хотел бы найти официальный источник в поддержку этого утверждения.

:: РЕДАКТИРОВАТЬ ::

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

Поэтому большинство моих аргументов здесь справедливы только для vanilla JS и современных браузеров.

Жюль Колле
источник