www.jQueryBook.ru - jQuery документация

 

 

 

 



События в jQuery: .on(), .off(), делегирование и объект event

Опубликовано: 26.09.2026 · Обновлено: 26.09.2026 · Актуально для jQuery 3.7.1 и 4.0.0

С версии 1.7 в jQuery остался один способ навесить обработчик — .on(), а снять его — .off(). Методы .bind(), .delegate() и .live() — это история: .live() удалён ещё в 1.9, .bind() и .delegate() с версии 3.0 считаются устаревшими, но пока работают и в jQuery 4.0. Если обработчик клика не срабатывает на элементах, добавленных через Ajax или шаблонизатор, — дело почти всегда в отсутствии делегирования: вместо $(".item").on("click", fn) нужно $(document).on("click", ".item", fn), где .item может появиться в DOM позже.

Эволюция API событий

За пятнадцать с лишним лет в jQuery сменилось три поколения методов для работы с событиями. Если вы поддерживаете старый проект, полезно понимать, что перед вами: рабочий, но устаревший код или уже нерабочий после обновления библиотеки.

МетодДобавленСтатусЗамена
.bind() / .unbind()1.0Deprecated с 3.0, работает в 4.0.on() / .off()
.live() / .die()1.3Deprecated в 1.7, удалён в 1.9делегированный .on()
.delegate() / .undelegate()1.4.2Deprecated с 3.0, работает в 4.0.on() с селектором
.on() / .off()1.7Актуальный способ—
Шорткаты .click(), .change(), .hover() и т. п. (полный список — все события jQuery)1.0Deprecated с 3.3, работают в 4.0.on("click", fn) и т. д.

Разница между «deprecated» и «удалён» здесь принципиальна. .live() и .die() физически исчезли из кода jQuery в версии 1.9 — вызов бросит TypeError: $(...).live is not a function. А вот .bind(), .delegate() и событийные шорткаты в списке deprecated с 3.0/3.3, но при обновлении на jQuery 4.0.0 в январе 2026 года их не тронули: в разделе «Deprecated APIs removed» апгрейд-гайда к 4.0 перечислены утилиты вроде jQuery.type, jQuery.isArray, jQuery.trim и несколько внутренних методов — событийных методов там нет. Это не гарантия на будущее (в jQuery Migrate 4.x эти вызовы по-прежнему помечаются предупреждением), но прямо сейчас переписывать рабочий .bind() ради самого факта устаревания не обязательно — если только вы не готовите проект к следующему мажорному релизу.

Синтаксис .on()

У .on() два основных варианта вызова.

// Обычная привязка
$(elem).on("click", function (event) {
  console.log("клик по", this);
});

// С делегированием — второй аргумент "selector"
$(parent).on("click", ".item", function (event) {
  console.log("клик по элементу .item внутри parent");
});

Второй вариант — с картой событий вместо строки и функции: ключи объекта — имена событий, значения — обработчики.

$(elem).on({
  click: function (event) {
    console.log("click");
  },
  mouseenter: function (event) {
    console.log("mouseenter");
  }
});

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

$(elem).on("focus blur", function (event) {
  console.log(event.type); // "focus" или "blur"
});

Между именем событий и обработчиком можно передать произвольные данные — они попадут в обработчик как event.data, причём ровно те же самые для каждого вызова (не путайте с данными, которые нужно вычислять на каждый клик заново):

$(elem).on("click", { userId: 42 }, function (event) {
  console.log(event.data.userId); // 42
});

Внутри обработчика, объявленного через function, this указывает на DOM-элемент, на котором сработал обработчик, — то же самое, что event.currentTarget, только не обёрнутое в jQuery ($(this) даёт jQuery-объект). Если обработчик — стрелочная функция, this берётся из внешнего контекста, а не из события, и внутри неё придётся использовать только event.currentTarget:

// this — DOM-элемент кнопки
$(".btn").on("click", function (event) {
  $(this).toggleClass("active");
});

// this здесь НЕ элемент кнопки — это ловушка стрелочных функций
$(".btn").on("click", (event) => {
  $(event.currentTarget).toggleClass("active");
});

Делегирование событий

Делегирование решает две задачи: обработчики для элементов, которых ещё нет на странице, и меньше обработчиков вместо тысяч одинаковых. Работает это на всплытии (event bubbling) — большинство DOM-событий поднимается от цели вверх по дереву предков, и jQuery ловит их на общем родителе, а затем сверяет, попадает ли исходная цель под указанный CSS-селектор.

<table id="orders">
  <tbody>
    <!-- строки добавляются через Ajax -->
  </tbody>
</table>

<script>
// Один обработчик на всю таблицу, а не на каждую строку
$("#orders").on("click", "tr.order-row", function (event) {
  var orderId = $(this).data("order-id");
  console.log("открыть заказ", orderId);
});

// Строки добавляются позже — обработчик всё равно сработает
$.get("/api/orders", function (rows) {
  $("#orders tbody").append(rows);
});
</script>

Если после Ajax-запроса клик по новым элементам «не работает» — почти всегда это значит, что обработчик повешен напрямую на сами элементы ($(".order-row").on(...)) до того, как они появились в DOM. Прямая привязка работает только с уже существующими на момент вызова элементами; для будущих нужен делегированный вариант с селектором вторым аргументом у общего, статично существующего родителя.

В обработчике различаются три связанных, но разных элемента:

  • event.target — самый глубокий элемент, где реально произошло событие (может быть, например, <span> внутри строки таблицы);
  • event.currentTarget — элемент, на котором jQuery сейчас выполняет обработчик (при делегировании — это найденный по селектору потомок, а не тот, на котором вызван .on());
  • event.delegateTarget — элемент, к которому обработчик был фактически привязан через .on() (в примере выше — #orders).

Не все события всплывают одинаково удобно для делегирования. focus и blur в браузере не всплывают вовсе — для делегирования нужно подписываться на всплывающие пары focusin/focusout. В jQuery до 4.0 они дополнительно эмулировались (шимились) для старых браузеров, где нативной поддержки не было; в 4.0.0 эту эмуляцию убрали как ненужную для актуальных браузеров — событие работает нативно.

mouseenter и mouseleave тоже не всплывают по спецификации, но их делегированную версию jQuery продолжает эмулировать сама: внутри она вешает на общий обработчик mouseover/mouseout и проверяет event.relatedTarget, чтобы вызвать ваш код только при реальном входе/выходе курсора за границы совпавшего элемента, а не при переходе между его дочерними узлами. Для вашего кода это прозрачно — $(parent).on("mouseenter", ".card", fn) работает так, как ожидается, просто дороже по производительности, чем прямой обработчик, если карточек мало и они статичны.

СобытиеВсплывает нативноДелегирование через .on()
click, keydown, submit, changeдаработает напрямую
focus, blurнетподписывайтесь на focusin/focusout
mouseenter, mouseleaveнетработает — jQuery эмулирует поверх mouseover/mouseout

Обратная сторона делегирования — производительность на очень общих селекторах. Официальная документация прямо предупреждает: навешивание множества делегированных обработчиков высоко в дереве документа (например, на document) с иерархическими селекторами (#list li a.link) может заметно замедлить обработку событий на страницах с большим DOM — простые селекторы вида tag#id.class проверяются намного быстрее. Практический вывод: делегируйте от ближайшего статичного контейнера, а не от document «на всякий случай».

Снятие обработчиков: .off()

.off() симметричен .on() и умеет снимать обработчики по-разному:

// Все обработчики click (и прямые, и делегированные) с элемента
$(elem).off("click");

// Только делегированные для конкретного селектора
$(parent).off("click", ".item");

// Только конкретная функция — остальные click остаются
function onItemClick(event) { /* ... */ }
$(parent).on("click", ".item", onItemClick);
$(parent).off("click", ".item", onItemClick);

// Вообще все обработчики любых событий с элемента
$(elem).off();

Чтобы не задеть чужие обработчики на том же элементе и событии, используйте пространства имён — произвольную метку после точки в имени события:

$(document).on("click.myWidget", ".toggle", onToggle);
$(document).on("keydown.myWidget", onEscClose);

// Снять только обработчики своего виджета, не трогая чужие click/keydown
$(document).off(".myWidget");

Это особенно важно в делегированных обработчиках на document или body: там часто «живут» события сразу нескольких независимых частей интерфейса, и снятие без пространства имён снесёт заодно и чужие подписки на то же имя события.

Отдельный источник утечек памяти и дублирующихся обработчиков — разница между .remove() и .detach(). .remove() удаляет элемент из DOM и одновременно чистит все привязанные к нему через jQuery данные и обработчики. .detach() тоже убирает элемент из DOM, но специально оставляет данные и обработчики нетронутыми — это сделано для того, чтобы можно было вставить тот же элемент обратно с уже готовыми обработчиками. Если вы используете .detach() для временного скрытия и потом создаёте новый элемент с нуля вместо повторной вставки старого, обработчики на отсоединённом узле останутся висеть в памяти до сборки мусора — на практике это редко критично, но стоит понимать разницу.

.one(): обработчик на один раз

.one() ведёт себя как .on(), включая делегирование, но сам снимает себя после первого срабатывания на конкретном элементе. Классический пример — защита формы от повторной отправки при двойном клике:

$("#order-form").one("submit", function (event) {
  event.preventDefault();
  var $form = $(this);
  $.post($form.attr("action"), $form.serialize(), function () {
    $form.find("button[type=submit]").prop("disabled", true).text("Отправлено");
  });
});

Если строка событий содержит несколько типов через пробел, .one() отработает по одному разу на каждый тип, а не один раз суммарно — $(elem).one("mouseenter mouseleave", fn) вызовет fn дважды: один раз при входе курсора, один раз при выходе.

Объект event

В обработчик jQuery всегда передаёт собственный объект jQuery.Event, а не «сырое» браузерное событие — это нормализует различия между браузерами и добавляет пару удобных методов. Оригинальное событие доступно через event.originalEvent.

Свойство / методЧто содержит
typeИмя события, например "click"
targetDOM-элемент, где событие реально произошло
currentTargetЭлемент, на котором сейчас выполняется обработчик
delegateTargetЭлемент, к которому был привязан обработчик через .on()
relatedTargetВторой причастный элемент (например, куда ушёл фокус при blur)
pageX, pageYКоординаты курсора относительно документа
whichКод клавиши/кнопки мыши — для клавиатуры в jQuery 4.0 без прежней шим-нормализации
key, codeНативные строковые обозначения клавиши — jQuery копирует их из originalEvent
dataДанные, переданные вторым аргументом в .on()
timeStampВремя создания события в миллисекундах
resultЗначение, которое вернул последний вызванный обработчик
originalEventНативный объект события браузера
preventDefault()Отменяет действие браузера по умолчанию (переход по ссылке, отправку формы)
stopPropagation()Останавливает всплытие события выше по дереву
stopImmediatePropagation()Останавливает всплытие и не даёт сработать остальным обработчикам на этом же элементе
isDefaultPrevented()true, если уже вызывали preventDefault()

Про event.which для клавиатуры стоит сказать отдельно. Раньше jQuery дополнительно нормализовал устаревшие keyCode/charCode, приводя их к единому which, чтобы сгладить различия между браузерами. В jQuery 4.0.0 эту часть шима убрали — она была нужна только для браузеров, которые давно не поддерживаются. Для определения нажатой клавиши сейчас надёжнее использовать нативное строковое имя — event.key (например, "Enter", "Escape"): jQuery копирует его (как и event.code) на свой объект события, так что обращаться к originalEvent для этого не нужно.

Фраза «return false из обработчика — это preventDefault + stopPropagation» не метафора: внутри jQuery обрабатывает возврат false именно так — вызывает оба метода за вас. Удобно для одной строчки, но неявно, поэтому в обработчиках со сложной логикой лучше вызывать оба метода явно и только там, где это действительно нужно.

Генерация событий: .trigger() и .triggerHandler()

.trigger() имитирует событие программно — не только вызывает привязанные через jQuery обработчики, но и запускает поведение браузера по умолчанию там, где оно есть: $("form").trigger("submit") действительно отправит форму, $("a").trigger("click") — выполнит переход по ссылке. Если нужно вызвать только обработчики, привязанные через jQuery, без побочных эффектов браузера и без всплытия по дереву, используйте .triggerHandler().

// Своё событие с дополнительными данными
$(document).on("cart:updated", function (event, cart) {
  console.log("товаров в корзине:", cart.count);
});

$(document).trigger("cart:updated", [{ count: 3 }]);

// Только обработчики jQuery, без всплытия и без действия браузера
$("#search-form").triggerHandler("submit");

Со стороны нативного JS то же самое пользовательское событие обычно создают через CustomEvent и element.dispatchEvent(). Так как .on() в конечном счёте вешает обработчик через addEventListener, jQuery-обработчик получит и такое, нативно отправленное событие. Данные, которые нативный код кладёт в detail, доступны в обработчике как event.detail — это свойство входит в список, который jQuery проксирует с исходного события (то же значение лежит в event.originalEvent.detail). Обратное неверно: дополнительные аргументы из .trigger("имя", [данные]) приходят в jQuery-обработчик отдельными параметрами, а нативные слушатели addEventListener их не увидят вовсе.

Формы и клавиатура

Для отправки формы стандартный обработчик — submit на самой форме, а не click на кнопке (у формы может быть несколько способов отправки, включая Enter в текстовом поле):

$("#login-form").on("submit", function (event) {
  event.preventDefault();
  // здесь — валидация и отправка через $.ajax/fetch
});

Для отслеживания ввода в текстовые поля есть выбор между change и input. change у текстовых полей срабатывает только при потере фокуса после изменения значения, input — на каждое изменение содержимого, включая вставку из буфера обмена и автозаполнение. Для живой валидации по мере ввода нужен именно input:

$("#email").on("input", function () {
  var valid = /\S+@\S+\.\S+/.test(this.value);
  $(this).toggleClass("is-invalid", !valid);
});

Для клавиатуры используются keydown (нажатие, повторяется при удержании), keyup (отпускание) и keypress. keypress считается устаревшим на уровне спецификации DOM — он никогда не срабатывал для клавиш без символа (стрелки, Backspace) и в современных браузерах помечен как нежелательный к использованию; для новых обработчиков предпочтительны keydown/keyup в связке с event.key.

Частые ошибки

ОшибкаЧто происходитКак избежать
Повторная привязка при каждом обновлении блокаОбработчики множатся, событие срабатывает 2, 3, N разДелегировать один раз от статичного родителя вместо привязки на каждый Ajax-рендер
Делегирование от document на всё подрядКаждое событие на странице проходит через лишнюю проверку селектора — заметно на больших DOMДелегировать от ближайшего статичного контейнера, а не от document
return false вместо явного preventDefault()Всплытие незаметно глушится, ломаются делегированные обработчики выше по деревуВызывать preventDefault() и stopPropagation() явно и раздельно
.on() вызван до готовности DOMНа момент вызова элемента ещё нет, обработчик не на что вешатьОборачивать в $(function () { ... }) или делегировать от элемента, который точно уже есть
Ожидание passive-слушателей от .on()jQuery не поддерживает опцию passive в .on() — предложение добавить её обсуждается в issue-трекере jQuery годами и не реализованоДля touchstart/touchmove/wheel, где важна пассивность, вешать обработчик нативно через addEventListener(type, fn, { passive: true })

То же самое на чистом JS

Если часть кода переводится на нативный JavaScript, соответствия такие:

jQueryНативный аналог
.on("click", fn)el.addEventListener("click", fn)
.off("click", fn)el.removeEventListener("click", fn)
.one("click", fn)el.addEventListener("click", fn, { once: true })
.on("click", ".item", fn) (делегирование)parent.addEventListener("click", e => { if (e.target.closest(".item")) fn(e); })
.trigger("click")el.dispatchEvent(new Event("click"))

Подробный разбор с примерами и нюансами разницы поведения — в статье события на чистом JavaScript. Про остальные изменения в новой версии библиотеки — в статье изменения в jQuery 4.0.

Чек-лист

  • Новый код — только через .on()/.off(); .bind()/.delegate() трогать не обязательно, если они уже работают, но и не размножать в новых местах.
  • Обработчик на элементы, которые появляются динамически (Ajax, шаблоны, инфинити-скролл), — всегда делегированный, от ближайшего статичного родителя.
  • Повторный рендер блока — не повод вызывать .on() заново: обработчик либо делегирован один раз, либо снимается через .off() перед повторной привязкой.
  • Публичные виджеты и плагины — обработчики только с пространством имён, снятие тоже по нему, а не голым .off("click").
  • preventDefault() и stopPropagation() — раздельно и явно, return false — только когда нужны оба эффекта одновременно.
  • Клавиатурные обработчики — по event.key, а не по устаревающей нормализации event.which.
  • Прокрутка/тач-события с высокой частотой — нативный addEventListener с passive: true, jQuery такой режим не поддерживает.

Частые вопросы

Чем .on() отличается от .click()?

.click(fn) — это шорткат, который под капотом вызывает .on("click", fn). Разницы в поведении нет, но с jQuery 3.3 такие событийные шорткаты (.click(), .hover(), .change() и т. д.) помечены как deprecated — в новом коде лучше сразу писать .on("click", fn), это явно показывает, что вы вешаете обработчик, а не эмулируете клик.

Почему обработчик срабатывает два раза?

Чаще всего потому, что .on() вызывается повторно на одном и том же элементе — например, внутри функции, которая перерисовывает блок после каждого Ajax-запроса. Каждый вызов добавляет ещё один обработчик, а не заменяет старый. Решение: делегировать обработчик один раз от статичного родителя, либо перед повторным .on() вызывать .off() с тем же пространством имён.

Как повесить обработчик на элементы, которых ещё нет в DOM?

Через делегирование: подписаться на статичный родительский элемент и указать селектор дочерних элементов вторым аргументом — $(parent).on("click", ".child", handler). jQuery слушает событие на parent и сверяет event.target с селектором на момент всплытия, поэтому сработает даже на элементе, которого не было в DOM в момент вызова .on().

Чем заменить .live()?

.live() удалён в jQuery 1.9, прямой замены в виде функции нет — нужно переписать вызов на делегированный .on(). $(sel).live("click", fn) становится $(document).on("click", sel, fn) (в идеале — не document, а ближайший статичный контейнер).

Как отменить отправку формы в обработчике?

Вызвать event.preventDefault() в обработчике события submit, повешенном через .on("submit", fn). Если нужно отменить отправку и одновременно не дать событию всплыть дальше — вызвать также event.stopPropagation() или использовать return false, который делает и то, и другое сразу.

Что использовать — return false или preventDefault?

return false внутри обработчика jQuery эквивалентен одновременному вызову event.preventDefault() и event.stopPropagation(). Это удобно, но часто отменяет всплытие незаметно для разработчика и ломает делегирование выше по дереву. Явные вызовы понятнее и предсказуемее — используйте return false только тогда, когда нужны оба эффекта сразу.

Работают ли .bind() и .delegate() в jQuery 4?

Да. В jQuery 4.0.0 из объявленных ранее deprecated API убрали в основном утилиты-дублёры нативного JavaScript вроде jQuery.type, jQuery.isArray и jQuery.trim — .bind(), .unbind(), .delegate(), .undelegate() и событийные шорткаты остались рабочими, хотя официально устарели с 3.0/3.3. Единственные реально удалённые из этой группы методы — .live() и .die(), это случилось ещё в 1.9.

[ наверх ]









 




Справочник по jQuery JavaScript API на русском языке