Обработка событий в jQuery — это привязка функции-обработчика к событию элемента (клик, отправка формы, нажатие клавиши) методом .on() и снятие ее методом .off(). Запись $("#btn").on("click", handler) означает: когда на кнопке произойдет click, jQuery вызовет handler и передаст ему объект события event. Ниже разберем все формы .on(), делегирование для элементов, которые появятся позже, поля event, отмену действия браузера и типичные ошибки.
Содержание
- Четыре понятия, которые путают
- Минимальный рабочий пример
- Синтаксис .on() и родственных методов
- Прямая и делегированная обработка
- Отмена действия и остановка всплытия
- Пространства имен, .one() и свои события
- Типичные ошибки
- Нативный аналог без jQuery
- Если не получилось
- Выводы
- Где применяется / связь с практикой
- FAQ
Примеры проверены 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, и браузер не считает такое событие действием пользователя.



