Обработка событий
Клиентские программы на языке JavaScript основаны на модели программирования, когда выполнение программы управляется событиями. При таком стиле программирования веб-браузер генерирует событие, когда с документом или некоторым его элементом что-то происходит. Например, веб-браузер генерирует событие, когда завершает загрузку документа, когда пользователь наводит указатель мыши на гиперссылку или нажимает клавишу на клавиатуре.
Если JavaScript-приложение интересует определенный тип события для определенного элемента документа, оно может зарегистрировать одну или более функций, которая будет вызываться при возникновении этого события. Имейте в виду, что это не является уникальной особенностью веб-программирования: все приложения с графическим интерфейсом пользователя действуют именно таким образом — они постоянно ожидают, пока что-то произойдет (т.е. ждут появления событий), и откликаются на происходящее.
— это строка, определяющая тип действия, вызвавшего событие. Тип «mousemove», например, означает, что пользователь переместил указатель мыши. Тип «keydown» означает, что была нажата клавиша на клавиатуре. А тип «load» означает, что завершилась загрузка документа (или какого-то другого ресурса) из сети. Поскольку тип события — это просто строка, его иногда называют именем события.
— это объект, в котором возникло событие или с которым это событие связано. Когда говорят о событии, обычно упоминают тип и цель события. Например, событие «load» объекта Window или событие «click» элемента . Самыми типичными целями событий в клиентских приложениях на языке JavaScript являются объекты Window, Document и Element, но некоторые типы событий могут происходить и в других типах объектов.
— это функция, которая обрабатывает, или откликается на событие. Приложения должны зарегистрировать свои функции обработчиков событий в веб-браузере, указав тип события и цель. Когда в указанном целевом объекте возникнет событие указанного типа, браузер вызовет обработчик. Когда обработчики событий вызываются для какого-то объекта, мы иногда говорим, что браузер «возбудил» или «сгенерировал» событие.
— это объект, связанный с определенным событием и содержащий информацию об этом событии. Объекты событий передаются функции обработчика события в виде аргумента (кроме IE8 и более ранних версий, где объект события доступен только в виде глобальной переменной event). Все объекты событий имеют свойство type, определяющее тип события, и свойство target, определяющее цель события.
Для каждого типа события в связанном объекте события определяется набор свойств. Например, объект, связанный с событиями от мыши, включает координаты указателя мыши, а объект, связанный с событиями от клавиатуры, содержит информацию о нажатой клавише и о нажатых клавишах-модификаторах. Для многих типов событий определяются только стандартные свойства, такие как type и target, и не передается никакой дополнительной полезной информации. Для таких типов событий важно само наличие происшествия события, и никакая другая информация не имеет значения.
— это процесс, в ходе которого браузер решает, в каких объектах следует вызвать обработчики событий. В случае событий, предназначенных для единственного объекта (таких как событие «load» объекта Window), надобность в их распространении отсутствует. Однако, когда некоторое событие возникает в элементе документа, оно распространяется, или «всплывает», вверх по дереву документа.
Дав определения некоторым терминам, можно перейти к изучению вопросов, связанных с событиями и их обработкой.
Регистрация обработчиков событий
Существует два основных способа регистрации обработчиков событий. Первый, появившийся на раннем этапе развития Всемирной паутины, заключается в установке свойства объекта или элемента документа, являющегося целью события. Второй способ, более новый и более универсальный, заключается в передаче обработчика методу объекта или элемента.
Дело осложняется тем, что каждый прием имеет две версии. Свойство обработчика события можно установить в программном коде на языке JavaScript или в элементе документа, определив соответствующий атрибут непосредственно в разметке HTML. Регистрация обработчиков вызовом метода может быть выполнена стандартным методом с именем addEventListener(), который поддерживается всеми браузерами, кроме IE версии 8 и ниже, и другим методом, с именем attachEvent(), поддерживаемым всеми версиями IE до IE9.
Установка свойств обработчиков событий
Самый простой способ зарегистрировать обработчик события заключается в том, чтобы присвоить свойству целевого объекта события желаемую функцию обработчика. По соглашению свойства обработчиков событий имеют имена, состоящие из слова «on», за которым следует имя события: onclick, onchange, onload, onmouseover и т.д. Обратите внимание, что эти имена свойств чувствительны к регистру и в них используются только строчные символы, даже когда имя типа события состоит из нескольких слов (например «readystatechange»). Ниже приводятся два примера регистрации обработчиков событий:
// Присвоить функцию свойству onload объекта Window. // Функция - обработчик события: она вызывается, когда документ будет загружен window.onload = function() < // Отыскать элемент var elt = document.getElementById("shipping_address"); // Зарегистрировать обработчик события, который будет вызываться // непосредственно перед отправкой формы elt.onsubmit = function() < return validate(this); >>
Такой способ регистрации обработчиков событий поддерживается во всех браузерах для всех часто используемых типов событий. Вообще говоря, все прикладные интерфейсы, получившие широкую поддержку, которые определяют свои события, позволяют регистрировать обработчики установкой свойств обработчиков событий.
Недостаток использования свойств обработчиков событий состоит в том, что они проектировались в предположении, что цели событий будут иметь не более одного обработчика для каждого типа событий. При создании библиотеки для использования в произвольных документах для регистрации обработчиков лучше использовать прием (такой как вызов метода addEventListener()), не изменяющий и не затирающий ранее зарегистрированные обработчики.
Установка атрибутов обработчиков событий
Свойства обработчиков событий в элементах документа можно также устанавливать, определяя значения атрибутов в соответствующих HTML-тегах. В этом случае значение атрибута должно быть строкой программного кода на языке JavaScript. Этот программный код должен быть не полным объявлением функции обработчика события, а только ее телом. То есть реализация обработчика события в разметке HTML не должна заключаться в фигурные скобки и предваряться ключевым словом function. Например:
Если значение HTML-атрибута обработчика события состоит из нескольких JavaScript-инструкций, они должны отделяться точками с запятой либо значение атрибута должно располагаться в нескольких строках.
Некоторые типы событий предназначены для браузера в целом, а не для какого-то конкретного элемента документа. Обработчики таких событий в языке JavaScript регистрируются в объекте Window. В разметке HTML они должны помещаться в тег , но браузер зарегистрирует их в объекте Window. Ниже приводится полный список таких обработчиков событий, определяемых проектом спецификации HTML5:
onafterprint onfocus ononline onresize onbeforeprint onhashchange onpagehide onstorage onbeforeunload onload onpageshow onundo onblur onmessage onpopstate onunload onerror onoffline onredo
При разработке клиентских сценариев обычно принято отделять разметку HTML от программного кода на языке JavaScript. Программисты, следующие этому правилу, избегают (или, по крайней мере, стараются избегать) использовать HTML-атрибуты обработчиков событий, чтобы не смешивать программный код на языке JavaScript и разметку HTML.
addEventListener()
В стандартной модели событий, поддерживаемой всеми браузерами, кроме IE версии 8 и ниже, целью события может быть любой объект — включая объекты Window и Document и все объекты Elements элементов документа — определяющий метод с именем addEventListener(), с помощью которого можно регистрировать обработчики событий для этой цели.
Метод addEventListener() принимает три аргумента. Первый — тип события, для которого регистрируется обработчик. Тип (или имя) события должен быть строкой и не должен включать префикс «on», используемый при установке свойств обработчиков событий. Вторым аргументом методу addEventListener() передается функция, которая должна вызываться при возникновении события указанного типа. В последнем аргументе методу addEventListener() передается логическое значение. Обычно в этом аргументе передается значение false. Если передать в нем значение true, функция будет зарегистрирована как перехватывающий обработчик и будет вызываться в другой фазе распространения события.
Спецификация со временем может измениться так, что будет допустимо опускать третий аргумент вместо того, чтобы явно передавать в нем значение false, но на момент написания этих строк отсутствие третьего аргумента в некоторых текущих браузерах приводила к ошибке.
Следующий фрагмент регистрирует два обработчика события «click» в элементе . Обратите внимание на различия двух используемых приемов:
Вызов метода addEventListener() со строкой «click» в первом аргументе никак не влияет на значение свойства onclick. Во фрагменте, приведенном выше, щелчок на кнопке приведет к выводу двух диалоговых окон alert(). Но важнее то, что метод addEventListener() можно вызвать несколько раз и зарегистрировать с его помощью несколько функций-обработчиков для одного и того же типа события в том же самом объекте. При появлении события в объекте будут вызваны все обработчики, зарегистрированные для этого типа события, в порядке их регистрации.
Многократный вызов метода addEventListener() для одного и того же объекта с теми же самыми аргументами не дает никакого эффекта — функция-обработчик регистрируется только один раз и повторные вызовы не влияют на порядок вызова обработчиков.
Парным к методу addEventListener() является метод removeEventListener(), который принимает те же три аргумента, но не добавляет, а удаляет функцию-обработчик из объекта. Это часто бывает удобно, когда необходимо зарегистрировать временный обработчик события, а затем удалить его в какой-то момент.
Internet Explorer версии ниже IE9 не поддерживает методы addEventListener() и removeEventListener(). В версии IE5 и выше определены похожие методы, attachEvent() и detachEvent(). Поскольку модель событий в IE не поддерживает фазу перехвата, методы attachEvent() и detachEvent() принимают только два аргумента: тип события и функцию обработчика, при этом в первом аргументе методам в IE передается имя свойства обработчика с префиксом «on», а не тип события без этого префикса.
Вызов обработчиков событий
После регистрации обработчика событий веб-браузер будет вызывать его автоматически, когда в указанном объекте будет возникать событие указанного типа. В этом разделе подробно описывается порядок вызова обработчиков событий, аргументы обработчиков, контекст вызова (значение this) и назначение возвращаемого значения обработчика. К сожалению, некоторые из этих подробностей отличаются между IE версии 8 и ниже и другими браузерами.
Аргумент обработчика событий
При вызове обработчика событий ему обычно (за одним исключением, о котором рассказывается ниже) передается объект события в виде единственного аргумента. Свойства объекта события содержат дополнительную информацию о событии. Свойство type, например, определяет тип возникшего события.
В IE версии 8 и ниже обработчикам событий, зарегистрированным установкой свойства, объект события при вызове не передается. Вместо этого объект события сохраняется в глобальной переменной window.event. Для переносимости обработчики событий можно оформлять, как показано ниже, чтобы они использовали переменную window.event при вызове без аргумента:
function handler(event) < event = event || window.event; // Здесь находится реализация обработчика >
Объект события передается обработчикам событий, зарегистрированным с помощью метода attachEvent(), но они также могут использовать переменную window.event.
При регистрации обработчика события посредством HTML-атрибута браузер преобразует строку с программным кодом на языке JavaScript в функцию. Браузеры, отличные от IE, создают функцию с единственным аргументом event. В IE создается функция, не принимающая аргументов. Если в таких функциях использовать идентификатор event, он будет ссылаться на window.event. В любом случае обработчики событий, определяемые в разметке HTML, могут ссылаться на объект события, используя идентификатор event.
Контекст обработчиков событий
Когда обработчик событий регистрируется установкой свойства, это выглядит как определение нового метода элемента документа:
e.onclick = function() < /* реализация обработчика */ >;
Поэтому нет ничего удивительного, что обработчики событий вызываются (с одним исключением, касающимся IE, которое описывается ниже) как методы объектов, в которых они определены. То есть в теле обработчика событий ключевое слово this ссылается на цель события.
В обработчиках ключевое слово this ссылается на целевой объект, даже когда они были зарегистрированы с помощью метода addEventListener(). Однако, к сожалению, это не относится к методу attachEvent(): обработчики, зарегистрированные с помощью метода attachEvent(), вызываются как функции, и в них ключевое слово this ссылается на глобальный (Window) объект. Эту проблему можно решить следующим способом:
/* Регистрирует указанную функцию как обработчик событий указанного типа в указанном объекте. Гарантирует, что обработчик всегда будет вызываться как метод целевого объекта. */ function addEvent(target, type, handler) < if (target.addEventListener) target.addEventListener(type, handler, false); else target.attachEvent("on" + type, function(event) < // Вызвать обработчик как метод цели, // и передать ему объект события return handler.call(target, event); >); >
Обратите внимание, что обработчики событий, зарегистрированные таким способом, нельзя удалить, потому что ссылка на функцию-обертку, передаваемую методу attachEvent(), нигде не сохраняется, чтобы ее можно было передать методу detachEvent().
Возвращаемые значения обработчиков
Значение, возвращаемое обработчиком события, зарегистрированным установкой свойства объекта или с помощью HTML-атрибута, следует учитывать. Обычно возвращаемое значение false сообщает браузеру, что он не должен выполнять действия, предусмотренные для этого события по умолчанию.
Например, обработчик onclick кнопки отправки формы может вернуть false, чтобы предотвратить отправку формы браузером. (Это может пригодиться, если ввод пользователя не прошел проверку на стороне клиента.) Аналогично обработчик события onkeypress поля ввода может фильтровать ввод с клавиатуры, возвращая false при вводе недопустимых символов.
Также важно значение, возвращаемое обработчиком onbeforeunload объекта Window. Это событие генерируется, когда браузер выполняет переход на другую страницу. Если этот обработчик вернет строку, она будет выведена в модальном диалоговом окне, предлагающем пользователю подтвердить свое желание покинуть страницу.
Важно понимать, что учитываются значения, возвращаемые обработчиками событий, только если обработчики зарегистрированы посредством установки свойств. Обработчики, зарегистрированные с помощью addEventListener() или attachEvent() вместо этого должны вызывать метод preventDefault() или устанавливать свойство returnValue объекта события.
Отмена событий
Значение, возвращаемое обработчиком события, зарегистрированным как свойство, можно использовать для отмены действий, выполняемых браузером по умолчанию в случае этого события. В браузерах, поддерживающих метод addEventListener(), отменить выполнение действий по умолчанию можно также вызовом метода preventDefault() объекта события. Однако в IE, версии 8 и ниже, тот же эффект достигается установкой свойства returnValue объекта события в значение false.
В следующем фрагменте демонстрируется обработчик события клика по гиперссылке, который использует все три способа отмены события (блокирует переход пользователя по ссылке):
window.onload = function() < // Найти все ссылки var a_href = document.getElementsByTagName('a'); // Добавить обработчик события click (не для IE<=8) for(var i = 0; i < a_href.length; i++) a_href[i].addEventListener('click', function(e) , false); >;
Текущий проект модуля «DOM Events 3» определяет в объекте Event свойство с именем defaultPrevented. Оно пока поддерживается не всеми браузерами, но суть его в том, что при обычных условиях оно имеет значение false и принимает значение true только в случае вызова метода preventDefault().
Отмена действий, по умолчанию связанных с событием, — это лишь одна из разновидностей отмены события. Имеется также возможность остановить распространение события. В браузерах, поддерживающих метод addEventListener(), объект события имеет метод stopPropagation(), вызов которого прерывает дальнейшее распространение события. Если в том же целевом объекте будут зарегистрированы другие обработчики этого события, то остальные обработчики все равно будут вызваны, но никакие другие обработчики событий в других объекта не будут вызваны после вызова метода stopPropagation().
В IE версии 8 и ниже метод stopPropagation() не поддерживается. Вместо этого объект события в IE имеет свойство cancelBubble. Установка этого свойства в значение true предотвращает распространение события.
Текущий проект спецификации «DOM Events 3» определяет в объекте Event еще один метод — метод с именем stopImmediatePropagation(). Подобно методу stopPropagation(), он предотвращает распространение события по любым другим объектам. Но кроме того, он также предотвращает вызов любых других обработчиков событий, зарегистрированных в том же объекте.
Что такое обработчики событий в JavaScript
Узнайте об обработчиках событий в JavaScript и научитесь создавать интерактивные веб-страницы с их помощью!
Алексей Кодов
Автор статьи
2 июня 2023 в 11:30
Обработчики событий являются ключевым элементом взаимодействия пользователя с веб-страницей и реализации динамического поведения. В этой статье мы разберем основы работы с обработчиками событий в JavaScript.
События и обработчики
Событие — это сигнал от браузера о том, что что-то произошло. Например, пользователь кликнул по элементу, нажал клавишу на клавиатуре или произошла ошибка загрузки изображения. Обработчик события — это функция, которая выполняется при возникновении определенного события.
Способы назначения обработчиков событий
Есть несколько способов назначения обработчиков событий на элементы страницы:
- Атрибуты HTML: можно добавить атрибут onclick (или другой соответствующий событию) прямо в разметку HTML.
- Свойства DOM-элемента: можно установить обработчик событий через свойство DOM-элемента.
let button = document.querySelector('button'); button.onclick = function() < alert('Клик!'); >;
- Метод addEventListener : является наиболее гибким и современным способом назначения обработчиков. Этот метод позволяет добавить несколько обработчиков событий на один элемент.
let button = document.querySelector('button'); button.addEventListener('click', function() < alert('Клик!'); >);
Веб-разработчик: новая работа через 9 месяцев
Получится, даже если у вас нет опыта в IT
Удаление обработчиков событий
Иногда возникает необходимость удалить обработчик события, например, чтобы предотвратить многократное выполнение функции. Для этого используется метод removeEventListener . Важно отметить, что для удаления обработчика необходимо использовать ту же функцию обработчика, которая была добавлена с помощью addEventListener .
function handleClick() < alert('Клик!'); >let button = document.querySelector('button'); button.addEventListener('click', handleClick); // Удаление обработчика события button.removeEventListener('click', handleClick);
Всплытие и перехват событий
События в JavaScript могут «всплывать» от вложенных элементов к родительским. Это означает, что обработчик события, назначенный на родительский элемент, может реагировать на события, происходящие внутри его дочерних элементов.
Используя метод addEventListener , можно указать фазу события, на которой должен сработать обработчик — всплытие (по умолчанию) или перехват. Перехват событий редко используется на практике, но может быть полезен в некоторых ситуациях.
element.addEventListener('click', handlerFunction, ); // Перехват события
В заключение, обработчики событий в JavaScript играют важную роль в создании интерактивных и динамических веб-страниц. Они позволяют реагировать на действия пользователя, обрабатывать события и добавлять дополнительное поведение элементам страницы. Успешная работа с обработчиками событий является одним из основных навыков веб-разработчика.
Создание обработчика событий
Дополнительные поля в почтовом уведомлении о новом заказе
Задача: добавить поля «комментарий киента к заказу», «телефон», «название службы доставки», «название способа оплаты», «полный адрес клиента» в шаблон письма-уведомления о новом заказе
Идея решения:
1. Добавляем новые поля в шаблон почтового события SALE_NEW_ORDER.
Поля добавляем в текст шаблона в виде
2. Добавляем в систему обработчик события «перед отправкой почтового уведомления о новом заказе».
3. В обработчике события заполняем добавленные в шаблон поля
Решение
Шаг 1. Шаблон SALE_NEW_ORDER
В шаблон добавил такой текст
. Контактный телефон: #PHONE# Комментарий: #ORDER_DESCRIPTION# Служба доставки: #DELIVERY_NAME# Способ оплаты: #PAY_SYSTEM_NAME# Адрес доставки: #FULL_ADDRESS# . |
Шаги 2 и 3. Код
в /bitrix/php_interface/init.php
вписал такой код
//-- Добавление обработчика события AddEventHandler("sale", "OnOrderNewSendEmail", "bxModifySaleMails"); //-- Собственно обработчик события function bxModifySaleMails($orderID, &$eventName, &$arFields) < $arOrder = CSaleOrder::GetByID($orderID); //-- получаем телефоны и адрес $order_props = CSaleOrderPropsValue::GetOrderProps($orderID); $phone=""; $index = ""; $country_name = ""; $city_name = ""; $address = ""; while ($arProps = $order_props->Fetch()) < if ($arProps["CODE"] == "PHONE") < $phone = htmlspecialchars($arProps["VALUE"]); >if ($arProps["CODE"] == "LOCATION") < $arLocs = CSaleLocation::GetByID($arProps["VALUE"]); $country_name = $arLocs["COUNTRY_NAME_ORIG"]; $city_name = $arLocs["CITY_NAME_ORIG"]; >if ($arProps["CODE"] == "INDEX") < $index = $arProps["VALUE"]; >if ($arProps["CODE"] == "ADDRESS") < $address = $arProps["VALUE"]; >> $full_address = $index.", ".$country_name."-".$city_name.", ".$address; //-- получаем название службы доставки $arDeliv = CSaleDelivery::GetByID($arOrder["DELIVERY_ID"]); $delivery_name = ""; if ($arDeliv) < $delivery_name = $arDeliv["NAME"]; >//-- получаем название платежной системы $arPaySystem = CSalePaySystem::GetByID($arOrder["PAY_SYSTEM_ID"]); $pay_system_name = ""; if ($arPaySystem) < $pay_system_name = $arPaySystem["NAME"]; >//-- добавляем новые поля в массив результатов $arFields["ORDER_DESCRIPTION"] = $arOrder["USER_DESCRIPTION"]; $arFields["PHONE"] = $phone; $arFields["DELIVERY_NAME"] = $delivery_name; $arFields["PAY_SYSTEM_NAME"] = $pay_system_name; $arFields["FULL_ADDRESS"] = $full_address; >
Полезные ссылки
Введение в события DOM
Список событий, возможных в DOM, очень длинный: click (клик мышью), touch (касание), load (загрузка), drag (перетягивание), change (изменение), input (ввод), error (ошибка), resize (изменение размера) и т.д. События могут срабатывать для любой части документа вследствие взаимодействия с ним пользователя или браузера. Они не просто начинаются и заканчиваются в одном месте; они циркулируют по всему документу, проходя свой собственный жизненный цикл. Это и делает события DOM столь гибкими и полезными. Разработчики должны понимать как работают события DOM, чтобы суметь использовать их потенциал и построить увлекательный интерфейс.
За всё время работы в фронтенде у меня сложилось впечатление, что ни один из просмотренных мной источников не дал чёткого объяснения как работают события DOM. В этой статье я хотел бы предоставить полный обзор этой темы, чтобы помочь вам освоить её быстрее, чем это удалось мне.
Я опишу основы работы с событиями DOM, затем углублюсь во внутренние аспекты работы и объясню как их можно использовать для решения часто встречающихся практических задач.
Обработка событий
Раньше процесс установки обработчиков событий для узлов дерева документа был довольно непоследовательным. Библиотеки вроде jQuery играли бесценную роль в абстрагировании от связанных с ним странностей.
Чем больше мы приближаемся к соответствию стандартам со стороны браузерных окружений, тем более безопасным становится использование API из официальной спецификации. Чтобы не усложнять, я расскажу как можно управлять событиями именно в современном вебе. Если вы пишите код на JavaScript для Internet Explorer (IE) 8 или старше, рекомендую для управления обработкой событий использовать полифил или фреймворк (такой как jQuery).
В JavaScript обработчик события можно установить используя следующее:
element.addEventListener(event-name>, callback>, use-capture>);
- event-name (название-события, строка). Это имя или тип события, которое вы хотите обработать. Им может быть любое стандартное событие DOM ( click , mousedown , touchstart , transitionEnd и т.д.) или же имя вашего пользовательского события ( о пользовательских событиях мы поговорим позже).
- callback (обработчик, функция). Эта функция вызывается когда происходит событие. Объект event , содержащий информацию о событии, присваивается в качестве первого параметра.
- use-capture (перехват, булево значение). Оно объявляет должен ли обработчик события вызываться в фазе «перехвата». (Не беспокойтесь, я объясню что это значит немного позже)
var element = document.getElementById('element'); function callback( ) < alert('Привет'); > // Добавление обработчика события element.addEventListener('click', callback);
Удаление приемника события
Удаление приемника события, когда он больше не нужен, считается хорошим тоном (особенно в веб-приложениях с длительным временем выполнения). Чтобы это сделать, используйте метод element.removeEventListener() :
element.removeEventListener(event-name>, callback>, use-capture>);
У removeEventListener , однако, есть один нюанс: нужно указать функцию обратного вызова, которая изначально была привязана к событию. Просто element.removeEventListener(‘click’); работать не будет.
По существу, если мы хотим иметь возможность удалять приемники событий (это необходимо делать для «долгоиграющих» приложений), нам нужно следить за нашими функциями обратного вызова. Это значит, что нельзя использовать анонимные функции.
var element = document.getElementById('element'); function callback( ) < alert('Привет один раз'); element.removeEventListener('click', callback); > // Добавление приемника события element.addEventListener('click', callback);
Поддержание контекста обработчика события в рабочем состоянии
Сбой в программе очень просто получить благодаря вызову обработчика события с некорректным контекстом. Объясним это на примере.
var element = document.getElementById('element'); var user = < firstname: 'Виктор', greeting: function( )< alert('Меня зовут ' + this.firstname); > >; // Присоединяем user.greeting в качестве обработчика события element.addEventListener('click', user.greeting); // alert => 'Меня зовут undefined'
Использование анонимных функций
Мы ожидали, что обработчик события выведет сообщение Меня зовут Виктор . На самом деле он выведет Меня зовут undefined . Чтобы this.firstName возвращало Виктор , нужно вызвать user.greeting в контексте (т.е. это то, что должно быть слева от точки) user .
Когда мы передаём функцию greeting методу addEventListener , мы всего лишь указываем отношение к функции; контекст user с ней не передается. По сути, функция вызвана в контексте element , это значит что this обозначает element , а не user . Следовательно, this.firstname не может быть определено.
Есть два способа предотвратить такой дисбаланс контекста. Во-первых, можно вызвать user.greeting() с правильным контекстом в анонимной функции.
element.addEventListener('click', function( ) < user.greeting(); // alert => 'Меня зовут Виктор' >);
Function.prototype.bind
Описанный выше метод недостаточно хорош, потому что когда мы хотим удалить функцию с помощью .removeEventListener() оказывается что у нас нет к ней доступа. Кроме того, такой подход довольно безобразный. Я предпочитаю использовать метод .bind() (встроен во все функции ECMAScript 5) для генерации новой функции (привязанной), которая всегда выполняется в заданном контексте. Затем мы передаём эту функцию методу .addEventListener() в качестве функции обратного вызова.
// Перекрытие исходной функции // функцией, привязанной к контексту 'user' user.greeting = user.greeting.bind(user); // Присоединение user.greeting в качестве функции обратного вызова button.addEventListener('click', user.greeting);
У нас также есть возможность сослаться на функцию обратного вызова, которую можно использовать, чтобы отвязать приемник события при необходимости.
button.removeEventListener('click', user.greeting);
Если нужно, можете зайти на страницу с информацией о поддержке Function.prototype.bind и страницу с описанием полифила.
Объект event
Объект event создаётся когда соответствующее событие происходит впервые; он сопровождает событие в его путешествии по дереву документа. Объект событие передаётся в качестве первого параметра функции, которую мы прописываем для приемника события как функцию обратного вызова. Этот объект можно использовать, чтобы получить доступ к огромному количеству информации о случившемся событии:
- type (строка). Это имя события.
- target (узел). Это узел DOM, который породил событие.
- currentTarget (узел). Это узел DOM, для которого на текущий момент работает обработчик события.
- bubbles (булево значение). Показывает является ли событие «всплывающим» (что это значит, я объясню позже).
- preventDefault (функция). Позволяет предотвратить любое поведение, установленное по умолчанию, со стороны агента пользователя (т.е. браузера) в отношении события (например, предотвращение загрузки новой страницы вследствие события click элемента ).
- stopPropagation (функция). Предотвращает запуск следующих обработчиков дальше по цепочке событий, однако не предотвращает запуск дополнительных обработчиков события с тем же именем для текущего узла. (Мы поговорим об этом позже.)
- stopImmediatePropagation (функция). Предотвращает запуск обработчиков для любых узлов дальше по цепочке событий, а также дополнительных обработчиков события с тем же именем для текущего узла.
- cancelable (булево значение). Указывает на то, можно ли с помощью метода event.preventDefault предотвратить запуск действий по умолчанию в ответ на событие.
- defaultPrevented (булево значение). Указывает был ли вызван метод preventDefault для объекта событие .
- isTrusted (булево значение). Событие называется доверенным, если оно исходит от самого устройства, а не синтезируется в JavaScript.
- eventPhase (число). Это число указывает фазу, в которой на данный момент находится событие: ни в какой ( 0 ), перехвата ( 1 ), цели ( 2 ) или всплытия ( 3 ). По фазам мы пройдёмся дальше.
- timestamp (число). Это дата, когда произошло событие.
Объект event может принимать множество других свойств, однако они зависят от конкретного типа события. Например, для событий мыши объекта event применяются свойства clientX и clientY чтобы определить размещение указателя в области просмотра.
Для более подробного ознакомления с объектом event и его свойствами лучше всего использовать отладчик в вашем любимом браузере или console.log .
Фазы события
Когда в приложении возникает событие DOM, оно не просто срабатывает один раз в месте своего происхождения; оно отправляется в путь, состоящий из трёх фаз. Вкратце, событие движется от корня документа к цели (фаза перехвата), затем срабатывает для цели события (фаза цели) и движется назад к корню документа (фаза всплытия).
Фаза перехвата
Первой фазой является фаза перехвата. Событие начинает своё путешествие в корне документа, проходит каждый слой дерева документа по направлению к цели, срабатывая для каждого узла по пути, пока её не достигнет. Задача фазы перехвата — наметить траекторию распространения, по которой событие будет двигаться в обратном направлении в фазе всплытия.
Как упоминалось ранее, обработчик можно вызвать в фазе перехвата, если в качестве третьего параметра для addEventListener указать true . Я особо не сталкивался с ситуациями, когда требовался вызов обработчика в фазе перехвата, однако, теоретически, с его помощью можно предотвратить срабатывание события щелчка мышью для определённого элемента если событие обрабатывается в фазе перехвата.
var form = document.querySelector('form'); form.addEventListener('click', function(event) < event.stopPropagation(); >, true); // Заметьте: 'true'
Если вы не уверены в позитивном результате, вызывайте обработчик события в фазе всплытия, указав последним параметром значение false или undefined .
Фаза цели
Момент, когда событие достигает конечного объекта, известен как фаза цели. Событие срабатывает для целевого узла перед тем, как изменить направление своего продвижения и вернуться назад к самому внешнему уровню документа.
В случае с вложенными элементами, события мыши и указателя мыши всегда нацелены на наиболее глубоко расположенный вложенный элемент. Если обработчик вызывается для события click элемента , и пользователь кликает по элементу
внутри , этот
становится целью события. Тот факт, что события «всплывают» значит, что можно вызывать обработчик для кликов по (или любому другому родительскому элементу) и получать функцию обратного вызова при прохождении события.
Фаза всплытия
После того, как событие сработало для цели, оно на этом не останавливается. Оно всплывает наверх (или же распространяется) по дереву документа, пока не достигнет его корня. Это значит, что то же самое событие срабатывает для родительского узла целевого элемента, затем для его родительского узла, и это продолжается пока не остаётся родительских элементов, для которых может сработать событие.
Дерево документа можно представить в виде луковицы, а цель события — в виде её сердцевины. В фазе перехвата событие проникает внутрь луковицы, минуя слой за слоем. Когда событие достигает сердцевины, оно срабатывает (фаза цели) и меняет направление на противоположное, слой за слоем прокладывая себе путь назад (фаза распространения).
Всплытие события — это очень полезное явление. Оно делает необязательным установку обработчика события для конкретного элемента, для которого происходит событие; вместо этого мы можем установить обработчик для элемента выше по дереву документа и подождать пока событие его достигнет. Если бы события не всплывали, нам, возможно, в некоторых случаях пришлось бы устанавливать обработчики для множества разных элементов для гарантии, что событие не останется незамеченным.
Большинство (но не все) событий всплывают. Если событие не всплывает, для этого наверняка есть веская причина. В случае сомнения по этому поводу, можете свериться со спецификацией.
Останавливаем распространение
Прервать распространение события в любой его момент (т.е. в фазе перехвата или всплытия) можно просто вызвав метод stopPropagation объекта событие . После этого событие не будет вызывать обработчики для узлов, которые он минует на своём пути к цели события и назад к корню документа.
child.addEventListener('click', function(event) < event.stopPropagation(); >); parent.addEventListener('click', function(event) < // Если произошёл клик мышью по дочернему элементу, // этот обработчик не будет вызван >);
Если для одного и того же события установлено несколько обработчиков, вызов event.stopPropagation() не предотвратит их срабатывания для текущей цели события. Если нужно предотвратить вызов любых дополнительных обработчиков для текущего узла, можно использовать более агрессивный метод event.stopImmediatePropagation() .
child.addEventListener('click', function(event) < event.stopImmediatePropagation(); >); child.addEventListener('click', function(event) < // Если произошёл клик мышью по дочернему элементу, // этот обработчик не будет вызван >);
Предотвращение поведения, установленного в браузере по умолчанию
Для некоторых событий, которые происходят в документе, в браузере установлено поведение по умолчанию. Наиболее распространённым является клик по ссылке. Когда для элемента происходит событие click , оно всплывает до корня документа, браузер расшифровывает атрибут href и перегружает окно с новой страницей.
В веб-приложениях обычно хотелось бы иметь возможность самостоятельно управлять навигацией, без перезагрузок страницы. Чтобы такую возможность получить, нужно предотвратить установленную по умолчанию реакцию браузера на клик, и вместо неё выполнить то, что задумали мы. Для этого мы вызовем event.preventDefault() .
anchor.addEventListener('click', function(event) < event.preventDefault(); // Выполнение нужных нам действий >);
Также можно предотвратить множество других установленных по умолчанию действий браузера. Например, можно запретить прокручивание страницы в игре на HTML5 при нажатии клавиши пробела, или предотвратить выделение текста при помощи кликов мышью.
Если вызвать event.stopPropagation() , мы всего лишь избежим вызова обработчиков, установленных для элементов дальше по цепочке распространения. Он не помешает браузеру выполнить свою работу.
Пользовательские события
Запустить событие DOM может не только браузер. Мы можем создать собственное пользовательское событие и применить его к любому элементу в документе. Событие такого типа ведёт себя точно так же, как обычное событие DOM.
var myEvent = new CustomEvent("myevent", < detail: < name: "Виктор" >, bubbles: true, cancelable: false >); // Вызов обработчика для события 'myevent' myElement.addEventListener('myevent', function(event) < alert('Привет, ' + event.detail.name); >); // Запуск события 'myevent' myElement.dispatchEvent(myEvent);
Также для имитации пользовательского взаимодействия можно синтезировать «не доверенные» события элементов (например, click ). Они могут пригодиться при тестировании библиотеки для DOM. Если вас это заинтересовало, проект Mozilla Developer Network предлагает описание работы с такими событиями.
- CustomEvent API не доступен в IE 8 и старше.
- В фреймворке Flight для Twitter пользовательские события используются для обмена данными между модулями. Такой подход привёл к крайне несвязной модульной архитектуре.
Делегированный обработчик события
Применение делегированного обработчика события является удобным и производительным способом обработки события большого количества узлов DOM при наличии одного обработчика. Например, если в списке есть 100 пунктов и они все должны реагировать на событие click одинаково, мы можем для каждого из них установить обработчик события. Это даст нам 100 отдельных обработчиков события. При каждом добавлении пункта в список нужно было бы устанавливать для него обработчик события click . Это не только дорого, но и неудобно.
-
. После клика по
событие всплывает до
-
и возбуждает обработчик события. По какому именно элементу
был произведён клик можно определить проверив event.target . Ниже в качестве иллюстрации приведён грубый пример:
var list = document.querySelector('ul'); list.addEventListener('click', function(event) < var target = event.target; while (target.tagName !== 'LI') < target = target.parentNode; if (target === list) return; > // Выполнение каких-то действий >);
Такой подход лучше, потому что мы имеем дело лишь с одним обработчиком события и не вынуждены обременять себя установкой новых обработчиков при каждом добавлении новых пунктов списка. Концепция довольно простая, но на удивление полезна.
В приложении такую грубую реализацию я бы не советовал использовать. Лучше обратиться к библиотекам JavaScript для делегирования событий, таким как ftdomdelegate от команды FT Labs. Если вы используете jQuery, то можете применить её встроенную возможность делегирования событий передавая методу .on() селектор в качестве второго параметра.
// Без делегирования события $('li').on('click', function( )<>); // Использование делегирования события $('ul').on('click', 'li', function( )<>);
Полезные события
load
Событие load происходит при окончании загрузки любого ресурса (в том числе зависимых ресурсов). Им может быть изображение, таблица стилей, скрипт, видео, аудио файл, документ или окно.
image.addEventListener('load', function(event) < image.classList.add('has-loaded'); >);
onbeforeunload
window.onbeforeunload даёт разработчикам возможность запросить у пользователя подтверждение намерения покинуть страницу. Это может пригодиться в приложениях, в которых изменения должны быть сохранены пользователем, иначе будут потеряны при закрытии вкладки браузера.
window.onbeforeunload = function( ) < if (textarea.value != textarea.defaultValue) < return 'Вы хотите покинуть страницу и отменить изменения?'; > >;
Обратите внимание, что установка обработчика onbeforeunload не позволяет браузеру кешировать страницу, что приводит к более длительной загрузке страницы при повторном посещении. Кроме того, обработчики onbeforeunload должны быть синхронизированы.
Избавление от подрагивания окна в Mobile Safari
В приложении Financial Times мы используем простой приём event.preventDefault для избежания подрагивания окна при прокрутке в Mobile Safari.
document.body.addEventListener('touchmove', function(event) < event.preventDefault(); >);
Стоит также знать, что это заблокирует любое встроенное прокручивание (такое как overflow: scroll ). Чтобы разрешить встроенное прокручивание для набора элементов, которые в нём нуждаются, следует установить обработчик для того же события элемента, который должен прокручиваться, и установить флаг для объекта события. В обработчике на уровне документа мы решаем нужно ли предотвратить действие по умолчанию для события касания, исходя из наличия флага isScrollable .
// Ниже по дереву мы устанавливаем флаг scrollableElement.addEventListener('touchmove', function(event) < event.isScrollable = true; >); // Выше по DOM проверяем наличие этого флага чтобы определить // нужно ли разрешить браузеру выполнить прокручивание document.addEventListener('touchmove', function(event) < if (!event.isScrollable) event.preventDefault(); >);
В IE 8 и старше нельзя управлять объектом события. Чтобы обойти эту проблему можно установить свойства для узла event.target .
resize
Возможность установить обработчик для события resize объекта window очень удобна при сложной отзывчивой верстке страницы. Добиться такой верстки только с помощью CSS не всегда возможно. Иногда приходится использовать JavaScript для расчёта и применения размера элементов. Когда изменяется размер окна или же ориентация устройства, нам нужно подстроить эти размеры.
window.addEventListener('resize', function( ) < // обновление верстки >);
Я рекомендую использовать обработчик с ограничением количества вызовов чтобы нормализировать частоту вызова обработчика и избежать слишком частого пересчёта верстки.
transitionEnd
Сегодня для реализации большинства переходов и анимации в наших приложениях мы используем CSS. Однако иногда нам все же нужно узнать когда выполнение определённой анимации подошло к концу.
el.addEventListener('transitionEnd', function( ) < // Выполнение каких-либо действий >);
Обратите внимание на следующее:
- При создании анимации с применением свойства @keyframe , используйте в качестве имени события animationEnd , а не transitionEnd .
- Как и большинство событий, transitionEnd всплывает. Помните, что нужно вызвать event.stopPropagation() для дочерних событий перехода, либо проверить event.target , чтобы предотвратить обработку событий, когда она не нужна.
- Для большинства имен событий всё еще широко используются вендорные префиксы (например, webkitTransitionEnd , msTransitionEnd , и т.д.). Используйте библиотеку вроде Modernizr для получения имен с правильными вендорными префиксами.
animationiteration
Событие animationiteration происходит при завершении каждой итерации для текущей анимации элемента. Оно пригодится когда нужно остановить анимацию, но только не в разгаре воспроизведения.
function start( ) < div.classList.add('spin'); > function stop( ) < div.addEventListener('animationiteration', callback); function callback( ) < div.classList.remove('spin'); div.removeEventListener('animationiteration', callback); > >
Если вы заинтересовались, я написал о событии animationiteration более подробно в своём блоге.
error
Если в процессе загрузки ресурса происходит ошибка, нам наверняка нужно на это как-то отреагировать, особенно если у наших пользователей плохое соединение с сетью. В приложении Financial Times используется событие error для определения изображений, которые не удалось загрузить в статье и их немедленного скрытия. Так как согласно спецификации «DOM Уровень 3 События (DOM Level 3 Events)» событие error всплывать не должно, обработать его можно одним из двух способов.
imageNode.addEventListener('error', function(event) < image.style.display = 'none'; >);
К сожалению, addEventListener подходит не во всех случаях. Мой коллега Kornel любезно представил мне пример, который доказывает что, к сожалению, единственный способ гарантировать вызов обработчика события error для изображения состоит в использовании строчных обработчиков события (хоть они и не приветствуются).
img src="http://example.com/image.jpg" onerror="this.style.display='none';" />
Причиной этому является то, что нельзя быть уверенным что код, с помощью которого установлен обработчик события error , будет выполнен до того, как собственно произойдёт событие error . Использование строчного обработчика значит, что наш обработчик события error будет установлен при обработке разметки и запросе изображения.
Выводы
Из успешности концепции событий DOM можно сделать определённые выводы. Мы можем использовать похожие концепции в наших собственных проектах. Модули в приложении могут быть настолько сложными, насколько это нужно, пока это не сказывается на простоте интерфейса. Большое количество фронтенд фреймворков (таких как Backbone.js) в большей мере основаны на событиях.
Архитектура, построенная на событиях великолепна. Она даёт нам простой и понятный интерфейс, в котором можно писать приложения, отзывчивые к физическим взаимодействиям на тысячах устройств! Посредством событий устройства с точностью говорят нам что произошло и когда, давая возможность отреагировать так, как мы пожелаем. То, что происходит «под капотом», нас не волнует; мы получаем тот уровень абстрагирования, который даёт нам полную свободу для создания великолепного приложения.
Материалы для дальнейшего чтения
- «Спецификация: Объектная модель документа уровень 3, События», W3C
- «Графическое представление распространения события по дереву документа с использованием потока события DOM» (изображение) W3C
- «Событие», Mozilla Developer Network
- «Хитрости в структуре DOM II», Дж. Дэвид Айзенберг (J. David Eisenberg), A List Apart
- «Таблицы совместимости событий», Quirksmode
Особую благодарность хочу выразить Kornel за отличный технический анализ.