События и делегирование событий

jQuery упрощает реагирование на действия пользователя с веб-страницей. Это означает, что вы можете написать код, который выполняется, как только пользователь щёлкает по определённой части страницы или когда он перемещает курсор за пределы элемента формы. Например, этот код отслеживает, что пользователь щёлкнул по любому элементу <li> на странице:

$( 'li' ).click(function( event ) {
  console.log( 'clicked', $( this ).text() );
});

Данный код выбирает все пункты списка на странице, затем связывает функцию обработчика с событием щелчка для каждого пункта списка с помощью метода .click().

Такие методы как .click(), .blur(), .change() и другие являются «сокращёнными» методами связывания событий. jQuery предлагает ряд таких сокращённых методов, каждый из которых соответствует родному событию DOM:

Родное имя события Сокращённый метод
click .click()
keydown .keydown()
keypress .keypress()
keyup .keyup()
mouseover .mouseover()
mouseout .mouseout()
mouseenter .mouseenter()
mouseleave .mouseleave()
scroll .scroll()
focus .focus()
blur .blur()
resize .resize()

Если залезть под капот, то все сокращённые методы в jQuery используются методом .on(). Вы можете включить метод .on() в свой код; в действительности, он даёт вам гораздо больше гибкости. При использовании .on() вы передаёте родное имя события в качестве первого аргумента, а затем функцию обработчика в качестве второго аргумента:

$( 'li' ).on( 'click', function( event ) {
  console.log( 'clicked', $( this ).text() );
});

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

$( 'li' ).trigger( 'click' );

Если у события, которое вы хотите инициировать, есть сокращённый метод (см. таблицу выше), вы также можете инициировать это событие, просто вызывая сокращённый метод:

$( 'li' ).click();

Когда применяется .trigger() вы только инициируете обработчики событий, которые связаны с JavaScript, но не инициируете поведение по умолчанию для этого события. К примеру, если вы инициируете событие click для элемента <a>, автоматического перехода по ссылке не произойдёт (хотя вы можете написать код, который будет это делать).

После того, как вы привязали событие, его можно отвязать с помощью метода .off(). Это позволит удалить любые обработчики, которые были привязаны к определённому событию:

$( 'li' ).off( 'click' );

Пространство имён событий

Одним из преимуществ, которые предлагает .on() — это возможность использовать «пространство имён» событий. Для чего вам требуется задействовать пространство имён? Рассмотрим ситуацию, когда вы привязываете некоторые события, а затем хотите отменить привязку каких-то обработчиков. Как мы это уже видели, вы можете сделать это таким образом:

Внимание! Неудачное решение

$( 'li' ).on( 'click', function() {
  console.log( 'щёлкнули по элементу списка' );
});
  
$( 'li' ).on( 'click', function() {
  registerClick();
  doSomethingElse();
});
  
$( 'li' ).off( 'click' );

Однако это отвяжет все обработчики click для всех элементов, чего нам не хочется. Если вы привяжете обработчик событий с помощью пространства имён событий, то можно задать конкретные обработчики событий:

$( 'li' ).on( 'click.logging', function() {
  console.log( 'щёлкнули по элементу списка' );
});
  
$( 'li' ).on( 'click.analytics', function() {
  registerClick();
  doSomethingElse();
);

$( 'li' ).off( 'click.logging' );

Данный код оставляет нетронутым click для analytics, в то же время отменяя click для logging.

Мы также можем использовать пространства имен, чтобы инициировать только определённые события:

$( 'li' ).trigger( 'click.logging' );

Привязка нескольких событий за раз

Ещё одним преимуществом .on() является возможность привязывать несколько событий одновременно. Например, вы можете выполнить один код, когда пользователь прокручивает окно или когда пользователь изменяет размеры этого окна. Метод .on() позволяет передавать оба события в виде строки с пробелом — а затем вызвать функцию, которая обработает оба события:

$( 'input[type="text"]' ).on('focus blur', function() {
  console.log( 'Для поля <input> получен или потерян фокус' );
});
  
$( window ).on( 'resize.foo scroll.bar', function() {
  console.log( 'Окно изменило размеры или прокручено!' );
});

Именованная функция как обработчик событий

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

var handleClick = function() {
  console.log( 'на что-то щёлкнули' );
};
  
$( 'li' ).on( 'click', handleClick );
  $( 'h1' ).on( 'click', handleClick );

Объект события

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

$( document ).on( 'click', function( event ) {
  console.log( event.type );    // Тип события, к примеру click
  console.log( event.which );   // Нажатая кнопка или клавиша
  console.log( event.target );  // Источник события
  console.log( event.pageX );   // Координата мыши по оси X
  console.log( event.pageY );   // Координата мыши по оси Y
});

Внутри обработчика событий

При указании функции, которая будет использоваться в качестве обработчика событий, эта функция получает доступ к исходному элементу DOM, который инициировал событие. Если вы желаете использовать jQuery для манипуляций с элементом, нужно передать его в $():

$( 'input' ).on( 'keydown', function( event ) {
  // this: Элемент, с которым связан обработчик событий
  // event: Объект события
  
 // Сменить цвет фона элемента <input> при нажатии на backspace 
  // на красный, в противном случае на зелёный
  $( this ).css( 'background', event.which === 8 ? 'red' : 'green' );
});

Отмена действия по умолчанию

Часто вам понадобится отменить действие события по умолчанию. Например, вы хотите обработать щелчок по элементу <a> с помощью AJAX, а не вызывать перезагрузку всей страницы. Чтобы добиться этого многие разработчики заставляют обработчик события возвращать false, но тут на деле есть один побочный эффект: он останавливает распространение события, что может иметь непредсказуемые последствия. Правильный способ отменить поведение события по умолчанию это вызвать метод .preventDefault() объекта события.

$( 'a' ).on( 'click', function( event ) {
  // Отменяем действие по умолчанию
  event.preventDefault();
  // Выводим
  console.log( 'По мне уже щёлкнули!' );
});

Это позволяет нам использовать «всплывающие» события, которые мы исследуем ниже.

Всплывающие события

Рассмотрим следующий код:

$( '*' ).add( [ document, window ] ).on( 'click', function( event ) {
  event.preventDefault();
  console.log( this );
});

Обработчик щелчка привязывается ко всем элементам в документе (чего вы никогда не должны делать в реальном коде), а также для document и window. Что произойдёт, когда вы щёлкните по элементу <a>, который находится внутри других элементов? На деле, событие click сработает для элемента <a>, а также для всех элементов, которые содержат <a> — весь путь до document и window.

Такое поведение называется всплывающее событие — событие срабатывает на элементе, по которому пользователь щёлкнул и если вы не вызовите .stopPropagation() для объекта события, то запустится весь путь вверх по DOM.

Вы можете увидеть это более наглядно, когда проанализируете такую разметку:

<a href="#foo"><span>Я ссылка!</span></a>
<a href="#bar"><b><i>И ты ссылка?</i></b></a>

И следующий код:

$( 'a' ).on( 'click', function( event ) {
  event.preventDefault();
  console.log( $( this ).attr( 'href' ) );
});

При щелчке по «Я ссылка!» вы на самом деле щёлкаете не на <a>, а на <span> внутри ссылки. Вместе с тем, всплывающее событие дойдёт до <a> и сработает связанный с ним обработчик click.

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

Поведение всплывающих событий позволяет нам делать «делегирование событий» — связывание обработчиков с элементами высокого уровня, а затем определение, какие элементы низкого уровня инициировали событие. Так, мы могли бы связать событие с маркированным списком, а затем выяснить, какой элемент инициировал событие:

$( '#my-unordered-list' ).on( 'click', function( event ) {
  console.log( event.target ); // записываем элемент, который вызвал событие
});

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

$( '#my-unordered-list' ).on( 'click', 'li', function( event ) {
  console.log( this ); // записываем элемент, по которому щёлкнули
});

$( '<li>новый пункт списка!</li>' ).appendTo( '#my-unordered-list' );

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

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

$( '#my-unordered-list' ).on( 'click', 'li', function( event ) {
  console.log( this ); // записываем элемент, на который щёлкнули
});

$( '<li>новый пункт списка!</li>' ).appendTo( '#my-unordered-list' );

Резюме

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

Автор: Ребекка Мёрфи
Последнее изменение: 27.02.2024