Должны ли все события jquery быть связаны с $(document)?

Откуда это идет

Когда я впервые изучил 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/

2 ответа

Решение

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

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

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

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

Вот случаи, когда делегирование события требуется или выгодно:

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

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

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

Он устанавливает универсальный обработчик событий jQuery на #myParent объект. Когда событие click вспыхивает до этого обработчика делегированных событий, jQuery должен просмотреть список обработчиков делегированных событий, прикрепленных к этому объекту, и посмотреть, соответствует ли исходный элемент для события любому из селекторов в обработчиках делегированных событий.

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

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


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

  1. Используйте делегированную обработку событий только тогда, когда она действительно предоставляет нужную вам функцию или повышает производительность. Не всегда используйте его, потому что это легко, потому что когда вам это не нужно. На самом деле он работает хуже во время отправки события, чем прямая привязка события.
  2. Присоедините делегированные обработчики событий к ближайшему родителю к источнику события, насколько это возможно. Если вы используете делегированную обработку событий, потому что у вас есть динамические элементы, для которых вы хотите захватывать события, выберите ближайшего родителя, который сам по себе не является динамическим.
  3. Используйте простые в оценке селекторы для делегированных обработчиков событий. Если вы следили за тем, как работает делегированная обработка событий, вы поймете, что делегированный обработчик событий нужно сравнивать с большим количеством объектов много раз, поэтому выбирайте как можно более эффективный селектор или добавляйте простые классы в ваши объекты, чтобы можно было использовать более простые селекторы. повысить производительность делегированной обработки событий.

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

Когда следует использовать делегирование событий?

  1. Когда вы связываете общий обработчик для большего количества элементов, которым требуется такая же функциональность. (Пример: наведение строки таблицы)
    • В этом примере, если бы вам пришлось связать все строки с помощью прямого связывания, вы бы в итоге создали n обработчиков для n строк в этой таблице. Используя метод делегирования, вы можете обработать все это в одном простом обработчике.
  2. Когда вы чаще добавляете динамическое содержимое в DOM (например: Добавить / удалить строки из таблицы)

Почему вы не должны использовать делегирование событий?

  1. Делегирование событий происходит медленнее по сравнению с привязкой события непосредственно к элементу.
    • Он сравнивает селектор цели на каждом пузыре, который попадает, сравнение будет таким же дорогим, как и сложным.
  2. Нет контроля над событием, пока оно не достигнет элемента, с которым оно связано.

PS: Даже для динамического содержимого вам не нужно использовать метод делегирования событий, если вы привязываете обработчик после того, как содержимое вставлено в DOM. (Если динамический контент добавляется не часто удаляется / повторно добавляется)

Я хотел бы добавить несколько замечаний и контраргументов к ответу 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 ('щелчок', 'button.actionButton', myFn); Он устанавливает общий обработчик событий jQuery на объект #myParent. Когда событие щелчка всплывает до этого делегированного обработчика событий, jQuery должен пройти через список делегированных обработчиков событий, прикрепленных к этому объекту, и посмотреть, соответствует ли исходный элемент для события любому из селекторов в делегированных обработчиках событий.

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

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

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

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

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

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

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

Видимо, сейчас действительно рекомендуется делегирование событий. по крайней мере, для vanilla js.

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

"Производительность в Интернете # Кажется, что прослушивание каждого щелчка в документе будет плохо для производительности, но на самом деле это более эффективно, чем наличие группы прослушивателей событий для отдельных элементов".

Другие вопросы по тегам