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

Обработка событий в jQuery: метод .on(), .off(), делегирование и объект event Полезное

Обработка событий в jQuery — это привязка функции-обработчика к событию элемента (клик, отправка формы, нажатие клавиши) методом .on() и снятие ее методом .off(). Запись $("#btn").on("click", handler) означает: когда на кнопке произойдет click, jQuery вызовет handler и передаст ему объект события event. Ниже разберем все формы .on(), делегирование для элементов, которые появятся позже, поля event, отмену действия браузера и типичные ошибки.

Примеры проверены 24.09.2026 на jQuery 4.0.0 (и повторно на 3.7.1) в jsdom 30.1 под Node.js 22. jQuery сегодня — в основном поддержка существующих проектов, поэтому в конце показан нативный аналог на addEventListener.

Четыре понятия, которые путают

Термин Что это Пример
Событие сигнал браузера о действии или изменении click, submit, keydown, input
Обработчик ваша функция, которую вызывают при событии function (event) { ... }
Объект события данные о конкретном срабатывании event.target, event.type
Действие по умолчанию то, что браузер делает сам переход по ссылке, отправка формы

Всплытие (bubbling): большинство событий после срабатывания на элементе поднимается к родителям до document и window. На этом построено делегирование.

Минимальный рабочий пример

Список задач: форма добавляет пункт, кнопка «x» удаляет. Файл jQuery лежит рядом со страницей.

<!DOCTYPE html>
<html lang="ru">
<head><meta charset="utf-8"><title>События jQuery</title></head>
<body>
  <form id="add-form">
    <label>Задача <input id="title"></label>
    <button type="submit">Добавить</button>
  </form>
  <ul id="tasks">
    <li>Купить молоко <button class="remove">x</button></li>
  </ul>
  <p id="log"></p>

  <script src="jquery-4.0.0.min.js"></script>
  <script>
    $(function () {
      // Прямой обработчик: висит на самой форме
      $("#add-form").on("submit", function (event) {
        event.preventDefault(); // не перезагружать страницу
        const title = $("#title").val().trim();
        if (!title) return;
        $("<li>").text(title + " ")
          .append('<button class="remove">x</button>')
          .appendTo("#tasks");
        $("#title").val("");
      });

      // Делегированный обработчик: висит на списке, ловит клики по .remove
      $("#tasks").on("click", ".remove", function () {
        const $li = $(this).closest("li");
        $("#log").text("Удалено: " + $li.contents().first().text().trim());
        $li.remove();
      });
    });
  </script>
</body>
</html>

Введите «Сдать отчет», нажмите «Добавить» — страница не перезагрузится, в списке станет два пункта. Нажмите «x» у нового пункта — он исчезнет, а в абзаце появится «Удалено: Сдать отчет». Удаление работает для пункта, которого не было при загрузке, потому что обработчик висит на <ul>.

Синтаксис .on() и родственных методов

Запись Что делает
.on("click", fn) прямой обработчик на выбранных элементах
.on("click", ".item", fn) делегированный: срабатывает для потомков, подходящих под .item
.on("click", null, {id: 7}, fn) передает данные в event.data
.on({click: fn1, mouseenter: fn2}) несколько событий одним объектом
.on("click keydown", fn) один обработчик на несколько типов через пробел
.one("click", fn) обработчик сработает один раз на каждом элементе
.off("click", fn) снять конкретный обработчик (нужна та же ссылка на функцию)
.off(".menu") снять все обработчики пространства имен menu
.trigger("click") вызвать обработчики программно

Сокращения вроде .click(fn) работают как .on("click", fn), но с jQuery 3.3 помечены устаревшими. Методы .bind(), .unbind(), .delegate() устарели с 3.0 (в 4.0.0 еще есть), а .live() удален в 1.9. В новом коде — только .on() и .off().

Прямая и делегированная обработка

Прямой обработчик привязан к элементам, найденным в момент вызова .on(). Элементы, добавленные позже, его не получат.

Делегированный обработчик вешается на постоянного предка (контейнер), а jQuery при каждом всплывшем событии проверяет, прошло ли оно через потомка, подходящего под селектор. Плюс — работает для будущих элементов и держит один обработчик вместо сотни. Цена — проверка селектора на каждое событие, поэтому контейнер берите ближайший постоянный, а не document.

Внутри обработчика важно различать четыре ссылки:

<ul id="menu">
  <li><a href="/about"><b>О нас</b></a></li>
</ul>
$("#menu").on("click", "a", { section: "menu" }, function (event) {
  event.preventDefault();
  console.log(event.type);                   // click
  console.log(event.target.tagName);         // B - где реально кликнули
  console.log(this.tagName);                 // A - элемент под селектором "a"
  console.log(event.currentTarget === this); // true
  console.log(event.delegateTarget.id);      // menu - где висит обработчик
  console.log(event.data.section);           // menu
  console.log(event.isDefaultPrevented());   // true
});

$("#menu b").trigger("click");

Клик по жирному тексту внутри ссылки дает target = <b>, а this = <a>. Если в делегированном обработчике опираться на event.target, код сломается, как только в ссылку добавят иконку или <span>. Нативный объект браузера доступен в event.originalEvent.

События focus и blur сами не всплывают, но jQuery при делегировании подменяет их на focusin и focusout, поэтому $("form").on("focus", "input", fn) работает, но event.type в таком обработчике равен focusin, а не focus (одинаково в jQuery 4.0.0 и 3.7.1) — не сравнивайте его со строкой "focus". В нативном коде такой подмены нет.

Отмена действия и остановка всплытия

Вызов Действие браузера Всплытие к родителям Другие обработчики этого элемента
event.preventDefault() отменено продолжается выполнятся
event.stopPropagation() выполнится остановлено выполнятся
event.stopImmediatePropagation() выполнится остановлено не выполнятся
return false в обработчике jQuery отменено остановлено выполнятся

return false в jQuery — это сразу preventDefault() и stopPropagation(). В нативном addEventListener возврат false ничего не отменяет. Останавливать всплытие без причины не стоит: это ломает делегированные обработчики выше по дереву, в том числе аналитику и закрытие выпадающих меню по клику снаружи.

Пространства имен, .one() и свои события

const $btn = $("#save");
$btn.on("click.analytics", () => console.log("аналитика"));
$btn.on("click.ui", () => console.log("интерфейс"));
$btn.one("click", () => console.log("только первый раз"));

$btn.trigger("click");  // аналитика, интерфейс, только первый раз
$btn.off(".analytics"); // снять обработчики этого пространства
$btn.trigger("click");  // интерфейс

// Свое событие с параметрами
$btn.on("task:done", (event, id, title) => console.log(id, title));
$btn.trigger("task:done", [7, "Отчет"]); // 7 Отчет

Пространство имен (click.analytics) — метка группы обработчиков. Она позволяет снять только свои обработчики, не трогая чужие плагины. Обработчики одного элемента вызываются в порядке привязки. Любое имя, не совпадающее со стандартным событием (task:done), jQuery считает пользовательским: оно срабатывает только через .trigger() или .triggerHandler().

Типичные ошибки

1. Прямой обработчик на элементах, которых еще нет.

Список — как в первом примере: один пункт с кнопкой «x».

$("#tasks .remove").on("click", function () {
  $(this).closest("li").remove();
});
$("#tasks").append('<li>Новая <button class="remove">x</button></li>');
$("#tasks .remove").last().trigger("click");
console.log($("#tasks li").length); // 2 - новый пункт не удалился

Обработчик привязан только к кнопке, найденной при вызове .on(). Исправление — делегирование: $("#tasks").on("click", ".remove", fn), после чего тот же сценарий печатает 1.

2. Повторная привязка при каждой инициализации.

function initSave() {
  $("#save").on("click", () => console.log("сохранено"));
}
initSave();
initSave(); // например, после перерисовки блока
$("#save").trigger("click"); // "сохранено" печатается дважды

Каждый вызов .on() добавляет новый обработчик, а не заменяет старый. Исправление — снимать свое перед привязкой: $("#save").off("click.save").on("click.save", fn). Тогда при двух вызовах initSave() строка печатается один раз.

3. Строка в data без селектора.

$("#save").on("click", "hello", function (event) {
  console.log(event.data);
});
$("#save").trigger("click"); // ничего не выведено

Если второй аргумент — строка, jQuery считает ее селектором и ищет потомков <hello>, которых нет. Исправление: явно передать null вместо селектора — .on("click", null, "hello", fn) — или передавать объект { msg: "hello" }.

Нативный аналог без jQuery

const list = document.querySelector("#tasks");
const controller = new AbortController();

list.addEventListener("click", (event) => {
  const btn = event.target.closest(".remove");
  if (!btn || !list.contains(btn)) return; // клик не по кнопке
  btn.closest("li").remove();
}, { signal: controller.signal });

// controller.abort(); // снять обработчик
jQuery Нативно
.on("click", fn) addEventListener("click", fn)
.on("click", ".remove", fn) обработчик на контейнере + event.target.closest(".remove")
.one("click", fn) addEventListener("click", fn, { once: true })
.off(".ns") AbortController и signal или removeEventListener с той же функцией
.trigger("task:done") dispatchEvent(new CustomEvent("task:done", { detail }))
delegate на focus focusin или addEventListener(..., { capture: true })

Проверка list.contains(btn) нужна, если список вложен в другой элемент с классом remove: closest() поднимается выше контейнера.

Если не получилось

  • Обработчик не срабатывает вовсе: скрипт выполнился до появления разметки. Оберните код в $(function () { ... }) или подключайте скрипт в конце <body>.
  • Срабатывает только на старых элементах: нужен делегированный обработчик на постоянном предке.
  • Срабатывает дважды: .on() вызвали повторно. Используйте .off("click.ns").on("click.ns", fn).
  • $ is not defined: jQuery не загружен или подключен после вашего скрипта.
  • Форма перезагружает страницу: нет event.preventDefault() в обработчике submit или ошибка выше по коду прервала обработчик до этой строки.

Выводы

  • .on() — единственный актуальный способ привязки в jQuery; .off() снимает обработчики, .one() дает однократный вызов.
  • Для элементов, добавляемых позже, нужен делегированный обработчик: .on("click", ".селектор", fn) на постоянном контейнере.
  • В делегированном обработчике this — элемент под селектором, event.target — точка клика, event.delegateTarget — контейнер.
  • return false в jQuery отменяет и действие браузера, и всплытие; для одного из них вызывайте конкретный метод.
  • Пространства имен защищают от двойной привязки и позволяют снимать только свои обработчики.

Где применяется / связь с практикой

Освойте тему на практике

Обработка событий нужна в любом интерактиве: формы, меню, модальные окна, фильтры каталога. На jQuery до сих пор держится много админок и CMS-тем, поэтому его событийную модель полезно читать, а новые компоненты писать на addEventListener или во фреймворке. Системно событийную модель браузера и DOM разбирают на курсе «JavaScript-разработчик. Базовый уровень». Попробовать формат можно на открытых уроках Otus.

FAQ

Чем .on() отличается от .bind()?
.bind() умел только прямую привязку и устарел с jQuery 3.0; .on() покрывает и прямую, и делегированную обработку. Код с .bind() работает и в 4.0.0, но при правках его лучше переводить на .on().

Можно ли вызвать .off() без аргументов?
Можно, но так снимутся все обработчики элемента, включая чужие плагины. Надежнее снимать по пространству имен: .off(".myWidget").

Срабатывает ли .trigger("click") как настоящий клик?
Он вызывает обработчики jQuery и, если у элемента есть одноименный нативный метод, действие браузера: .trigger("click") на чекбоксе переключит галочку. Исключение — ссылки: на <a> jQuery намеренно не вызывает нативный click(), перехода по href не будет, и слушатели addEventListener на ссылке не сработают. Но это программный вызов: event.originalEvent в обработчике будет undefined, и браузер не считает такое событие действием пользователя.

OTUS Журнал
Бесплатные открытые уроки (поп-ап)