Введение в события

Практически все JavaScript-приложения выполняют те или иные действия, откликаясь на различные события.

Событие - это сигнал от браузера о том, что что-то произошло.

Есть множество самых различных событий.

  • DOM-события, которые инициируются элементами DOM. Например, событие click происходит при клике на элементе, а событие mouseover - когда указатель мыши появляется над элементом,
  • События окна. Например событие resize - при изменении размера окна браузера,
  • Другие события, например load, readystatechange. Они используются, скажем, в технологии AJAX.

Именно DOM-события связывают действия, происходящие в документе, с кодом JavaScript, тем самым обеспечивая динамический веб-интерфейс.

Назначение обработчиков

Для того, чтобы скрипт реагировал на событие - нужно назначить хотя бы одну функцию-обработчик. Обычно обработчики называют "on+имя события", например: onclick.

Нужно сразу отметить, что JavaScript - однопоточный язык, поэтому обработчики всегда выпоняются последовательно и в общем потоке. Это значит, что при установке обработчиков двух событий, которые возникают на элементе одновременно, например mouseover (мышь появилась над элементом) и mousemove (мышь двигается над элементом), их обработчики будут выполнены последовательно.

Существует несколько способов назначать обработчик на конкретное событие элемента. Все они представлены ниже.

Через атрибут HTML-тега

Обработчик события можно указать в виде inline-записи, прямо в атрибуте onсобытие.

Например, для обработки события click на кнопке input, можно назначить обработчик onclick вот так:

<input id="b1" value="Нажми Меня" onclick="alert('Спасибо!');" type="button"/>

Этот код в действии:

Можно назначить и функцию.

Например, пусть при клике на кнопку input запускается функция count_rabbits(). Для этого запишем вызов функции в атрибут onclick:

<html>

    <head>
        *!*
        <script type="text/javascript">
            function count_rabbits() {
                for(var i=1; i<=3; i++) {
                   // оператор + соединяет строки
                   alert("Из шляпы достали "+i+" кролика!")
                }
            }
         </script>
         */!*
    </head>

    <body>
         *!*<input type="button" onclick="count_rabbits()" value="Считать кролей!"/>*/!*
    </body>

</html>

Напомним, что имена атрибутов HTML-тегов нечувствительны к регистру, поэтому атрибут oNcLiCk сработает также, как onClick или onclick.

Но если вы хотите придерживаться хорошего стиля (или спецификации XHTML), то имена тегов и их атрибуты должны быть указаны в нижнем регистре.

Когда использовать

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

У этого способа установки обработчика есть и минусы. Как только обработчик начинает занимать больше одной строки - читабельность резко падает.

Впрочем, сколько-нибудь сложные обработчики в HTML никто не пишет. Вместо этого лучше устанавливать обработчики из JavaScript способами, которые будут представлены ниже.

  • Просто для простых задач
  • Смесь javascript-кода и HTML-разметки
  • Сложные обработчики писать сложно или невозможно

Через свойство объекта

Самый близкий родственник описанного выше способа - установка функции-обработчика через свойство onсобытие соответствующего элемента. Этот способ тоже будет работать в любом браузере с поддержкой JavaScript.

Для этого нужно:

  1. получить элемент
  2. назначить обработчик свойству on+имя

Вот пример установки обработчика события click на элемент с id="myElement":

document.getElementById('myElement').onclick = function() {
    alert('Спасибо')
}
<input id="myElement" type="button" value="Нажми меня"/>

Этот код в действии:

Стоит сразу обратить внимание на две детали:

  1. Это именно свойство, а не атрибут. Поэтому, хотя технически и есть кроссбраузерные способы назначать обработчики через setAttribute, но лучше их даже не знать, а пользоваться прямым присвоением.

    Кроме того, как и все свойства объектов JavaScript, имя свойства onсобытие чувствительно к регистру символов и должно быть в нижнем регистре.

  2. Обработчик - не текст, а именно функция javascript.

Когда браузер видит свойство on... в HTML-разметке - он создает функцию из содержимого кавычек.

В этом смысле эти два кода работают одинаково:

  1. Только HTML:
    <input type="button" onclick=" alert('Клик!') "/>
    
  2. HTML + JS:
    <input type="button" id="button"/>
    
    document.getElementById('button').onclick = function() {
         alert('Клик')
    }
    
Все вызовы типа getElementById должны запускаться после описания соответствующего HTML-узла, а лучше - после окончания загрузки страницы.

Иначе узел просто не будет найден.

Конечно, можно и не создавать анонимную функцию, а использовать любую уже готовую:

function doSomething() {
	alert('Спасибо')
}

document.getElementById('button').onclick = doSomething
Частая ошибка новичков

Обратите внимание - свойству присваивается именно сама функция-обработчик doSomething, а не doSomething():

document.getElementById('button').onclick = doSomething

doSomething() - это результат запуска функции, а так как вызова return в ее коде нет, то этот результат будет undefined.

Сравните это с атрибутом. Там - наоборот, скобки нужны:

<input type="button" id="mybutton" onclick="doSomething()"/>

Это различие легко объяснить. Дело в том, что при назначении onclick в HTML браузер автоматически создает функцию-обработчик из содержимого кавычек. Получается, что последний пример - это по сути то же самое, что:

document.getElementById('mybutton').onclick = function() {
     doSomething()  // внутри автосозданной функции
}

Когда использовать

Описанная установка обработчика через свойство - очень популярный и простой способ.

У него есть один недостаток: на элемент можно повесить только один обработчик нужного события.

Например:

input.onclick = function() { alert(1) }
// ...
input.onclick = function() { alert(2) }  // заменит предыдущий
  1. Удобный и надежный способ, работает из javascript
  2. Только один обработчик на событие

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

Специальные методы

Представленных выше методов недостаточно для случаев, которые возникают при разработке серьёзного JavaScript-приложения.

Классический пример - установка обработчика на событие "содержимое окна загрузилось":

// разные элементы интерфейса могут иметь интерес 
// в том, чтобы их вызвали при загрузке документа
window.onload = function() {
   alert('Документ загружен!')
}
Если заведомо нет локальной переменной onload, то можно и не упоминать про window. Пустячок, но код немного короче.

onload = function() { ... }

Существует два основных интерфейса для установки событий.

Решение Microsoft

Методы, предложенные Microsoft, работают только в браузерах Internet Explorer и Opera(она поддерживает метод Microsoft для лучшей совместимости).

Установка обработчика:

element.attachEvent( "on"+имя события, обработчик)

Удаление обработчика:

element.detachEvent( "on"+имя события, обработчик)

Например:

var input = document.getElementById('b1')
var handler = function() {
    alert('Спасибо!')
}
input.attachEvent( "onclick" , handler) // поставить обработчик
// .... 
input.detachEvent( "onclick", handler) // убрать обработчик
Частая ошибка новичков

Обратите внимание - установка и удаление обработчика оперируют одной и той же функцией handler.

Так было бы неправильно:

input.attachEvent( "onclick" ,
   function() {alert('Спасибо')}
)
// .... 
input.detachEvent( "onclick", 
   function() {alert('Спасибо')}
)
// function(){} создает две разные функции

Поэтому при назначении обработчика, если вы планируете его потом снимать - необходимо где-то сохранить указатель на функцию.

Как уже говорилось ранее, вы можете установить несколько обработчиков на одно событие одного элемента:

var myElement2 = document.getElementById("myElement2");
var handler = function() {
	alert('Спасибо!');
}

var handler2 = function() {
	alert('Еще раз спасибо!');
}

myElement2.attachEvent("onclick", handler);
myElement2.attachEvent("onclick", handler2);
<input id="myElement2" type="button" value="Нажми меня"/>

Этот код в действии (будет работать только в Internet Explorer/Opera):

Установка по стандарту W3C

Решение W3C работает во всех современных браузерах, кроме Internet Explorer.

Установка обработчика:

element.addEventListener( имя_события, обработчик, фаза)

Удаление обработчика:

element.removeEventListener( имя_события, обработчик, фаза)

Обратите внимание, что имя события указывается без префикса "on".

Еще одно отличие от решения Microsoft это третий параметр – фаза.
Если он установлен в true, то при срабатывании события во вложенном элементе, обработчик будет вызван на фазе "перехвата", а если значение будет false, то - на фазе "всплывания". Подробнее об этом будет написано далее, в разделе этой статьи "Порядок срабатывания событий".

При обычной установке обработчика третий параметр всегда должен быть false.

Использование - аналогично решению от Microsoft:

// ... объявить функцию-обработчик handler ...
input.addEventListener( "click" , handler, false) // поставить обработчик
// .... 
input.removeEventListener( "click", handler, false) // убрать обработчик

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

  1. Сколько угодно обработчиков
  2. Кросс-браузерные несовместимости

Далее мы вернемся к различным способам установки обработчиков и в подробностях рассмотрим, как сделать все кросс-браузерно.

Объект "событие" (event)

Объект событие всегда передается обработчику и содержит массу полезной информации о том где и какое событие произошло.

Способов передачи этого объекта обработчику существует ровно два, и они зависят от способа его установки и от браузера.

W3C

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

Например:

function doSomething(event) {
	// event - будет содержать объект события
}

element.onclick = doSomething;

При вызове обработчика объект события event будет передан ему первым аргументом.

Можно назначить и вот так:

element.onclick = function(event) {
	// event - объект события
}

Интересный побочный эффект - в возможности использования переменной event при назначении обработчика в HTML:

<input type="button" onclick="alert(event)" value="Жми сюда не ошибешься"/>

Это работает благодаря тому, что браузер автоматически создает функцию-обработчик с данным телом, в которой первый аргумент event.

Internet Explorer

В Internet Explorer существует глобальный объект window.event, который хранит в себе информацию о последнем событии. А первого аргумента обработчика просто нет.

То есть, все должно работать так:

// обработчик без аргументов
function doSomething() {
	// window.event - объект события
}

element.onclick = doSomething;

Обратите внимание, что доступ к event при назначении обработчика в HTML (см. пример выше) по-прежнему будет работать. Такой вот надежный и простой кросс-браузерный доступ к объекту события.

Кросс-браузерное решение

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

function doSomething(event) {
    event = event || window.event

    // Теперь event - объект события во всех браузерах.
}

element.onclick = doSomething

Получение события при inline-записи

Как мы уже говорили раньше, при описании обработчика события в HTML-разметке для получения события можно использовать переменную с названием event.

<input type="button" onclick="alert(event.type)" value="Нажми меня"/>

Этот код в действии:

Это совершенно кросс-браузерный способ, так как по стандарту event - название первого аргумента функции-обработчика, которую автоматом создаст браузер; ну а в IE значение event будет взято из глобального объекта window.

Что дает объект события?

Из объекта события обработчик может узнать, на каком элементе оно произошло, каковы были координаты мыши (для событий, связанных с мышью), какая клавиша была нажата (для событий, связанных с клавиатурой), и извлечь другую полезную информацию.

Например, для события по клику мыши (onclick), свойство event.target(в IE event.srcElement) содержит DOM-элемент, на котором этот клик произошел.

Более подробно это описано в следующей статье Свойства объекта событие.

Порядок срабатывания событий

Примечательно, что на одно событие может реагировать не только тот элемент, на котором произошло событие, но и элементы над ним.

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

Рассмотрим ситуацию, когда у вас есть три элемента "вложенных" друг в друга.

1

2

3
<div class="d1" >1<!-- самый верхний, в представлении DOM, элемент -->
    <div class="d2">2
        <div class="d3">3</div><!-- самый глубокий элемент -->
    </div>
</div>

Если на каждом из них будет свой обработчик события, например onclick, то обработчик для какого элемента будет вызван первым при клике, скажем, на d3?

Всего существует 2 модели поведения, они не имеют преимуществ между собой, но используют принципиально разные подходы. Стандарт W3C объединяет две модели в одну универсальную.

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

Bubbling events order
В этой модели сначала будет выполнен обработчик на элементе 3, затем на элементе 2, и последним будет выполнен обработчик на элементе 1.

Такой порядок называется "всплывающим", потому что событие поднимается с самых "глубоких" элементов в представлении DOM, к самым "верхним", как пузырек воздуха в воде.

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

1

2

3
<div class="d1" onclick="alert(1)">
    <div class="d2" onclick="alert(2)">
        <div class="d3" onclick="alert(3)"></div>
    </div>
</div>

Остановка всплытия

Нужно понимать, что "всплытие" происходит всегда. При возникновении события на элементе, сигнал будет подниматься до самого высокого элемента, выполняя нужные обработчики.

Если какой-то обработчик хочет остановить всплытие и не выпускать событие дальше вверх - это делает следующий код:

element.onclick = function(event) {
    event = event || window.event // кросс-браузерно
    
    if (event.stopPropagation) {
        // Вариант стандарта W3C:
        event.stopPropagation()
    } else {
        // Вариант Internet Explorer:
        event.cancelBubble = true
    }
}

Можно уложить блок if/else в одну строчку:

event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true)

Перехват событий (Capturing)

Перехват - вторая, альтернативная всплытию модель порядка выполнения для события.

Capturing events order
В этой модели сначала будет выполнен обработчик на элементе 1, затем - на элементе 2 и последним будет выполнен обработчик на элементе 3. Она называется "перехват", потому что родительские элементы могут обработать событие раньше, чем непосредственная цель события, как бы "перехватывая" обработку.

Визуально это выглядит так (кликните на вложенном элементе, чтоб увидеть, какой будет порядок обработки события, не поддерживается в IE):

1

2

3
<div id="capt1" class="d1">
    <div id="capt2" class="d2">
        <div id="capt3" class="d3"></div>
    </div>
</div>

<script>
	document.getElementById("capt1").addEventListener("click", function() { alert(1) }, true);
	document.getElementById("capt2").addEventListener("click", function() { alert(2) }, true);
	document.getElementById("capt3").addEventListener("click", function() { alert(3) }, true);
</script>

Такой порядок был предложен Netscape и никогда не поддерживался в Internet Explorer, поэтому в IE вы не сможете увидеть этот пример в действии.

Остальные браузеры поддерживают одновременно такой порядок и порядок всплытия, но из-за проблем с Internet Explorer де-факто его почти никто не использует.

Порядок обработки в стандарте W3C

Решение от W3C объединяет обе модели: перехват и всплытие в одну универсальную.

W3C events order

При совершении действия, сначала событие перехватывается, пока не достигнет конечного элемента, затем всплывает.

Таким образом, разработчик сам решает, когда должен срабатывать обработчик события – при перехвате или при всплытии.

Визуально это выглядит так (кликните на вложенном элементе, чтоб увидеть, какой будет порядок обработки события, не для IE):

1

2

3
<div id="capt1" class="d1">
    <div id="capt2" class="d2">
        <div id="capt3" class="d3"></div>
    </div>
</div>

<script>
	document.getElementById("capt1").addEventListener("click", function() { alert(1) }, true);
	document.getElementById("capt2").addEventListener("click", function() { alert(2) }, true);
	document.getElementById("capt3").addEventListener("click", function() { alert(3) }, true);
	document.getElementById("capt1").addEventListener("click", function() { alert(1) }, false);
	document.getElementById("capt2").addEventListener("click", function() { alert(2) }, false);
	document.getElementById("capt3").addEventListener("click", function() { alert(3) }, false);
</script>

Если в качестве третьего параметра функции addEventListener передать значение true, то событие будет срабатывать на фазе захвата, если false – то после окончания захвата, на фазе всплытия.

При установке обработчиков классическими методами (через свойство элемента или атрибут html тега) события всегда будут срабатывать на фазе всплытия.

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

Действие браузера по умолчанию

Браузер имеет своё собственное поведение по умолчанию для различных событий.

Например, клик по ссылке - сменить URL, клик правой кнопкой мыши - показать контекстное меню и т.п.

В ряде случаев реакцию браузера на событие можно убрать в обработчике.Для этого у IE и W3C есть, как и раньше, два по сути близких, но по виду разных способа:

element.onclick = function(event) {
    event = event || window.event 

    if (event.preventDefault) {
        // Вариант стандарта W3C: 
        event.preventDefault()
    } else {
        // Вариант Internet Explorer:
        event.returnValue = false
    }
}

Вместо if/else можно записать одну строчку:

..
event.preventDefault ? event.preventDefault() : (event.returnValue=false)
...

Некоторые поведения по умолчанию происходят до вызова обработчика события. В этом случае их, конечно же, отменить нельзя.

Например, при фокусировке на ссылке - браузер выделяет ее пунктирной рамочкой.
Это действие выполняется до события focus, поэтому отменить выделение в обработчике onfocus нельзя.

А переход по ссылке выполняется после события, поэтому его отменить можно.
Обработчики onfocus и onclick на этой ссылке отменяют поведение по умолчанию:

Кликни меня

При клике перехода не произойдет, а рамка вокруг ссылки появится.

Код примера:

var a = document.getElementById('my-focus-a')
a.onfocus = a.onclick = function(e) {
    e = e || window.event
    // другая кроссбраузерная запись остановки события
    e.preventDefault ? e.preventDefault() : (e.returnValue=false)
}

Смысл return false из обработчика

Возвращение return false из обработчика события предотвращает действие браузера по умолчанию, но не останавливает всплытие. В этом смысле следующие два кода эквивалентны:

function handler(event) {
  ...
  return false
}
function handler(event) {
  ...
  if (event.preventDefault) {
    event.preventDefault() 
  } else {
    event.returnValue = false
  }
}

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

elem = document.getElementById('TestStop')

function handler(e) {
  e.preventDefault() // браузер - стоять
  e.stopPropagation() // событие - не всплывать
  return false // и вообще, мне больше ничего не надо
}

elem.addEventListener('click', handler, false)

// следующий обработчик все равно сработает
elem.addEventListener('click', function() { alert('А я сработало..') }, false);

Проверить:

Действительно, браузер даже не гарантирует порядок, в котором сработают обработчики на одном элементе. Назначить можно в одном порядке, а сработают в другом.

Поэтому тем более один обработчик никак не может влиять на другие того же типа на том же элементе.

Резюме

Вы узнали...

  • Что такое события и как они работают.
  • Как назначать обработчики - от HTML до специальных методов.
  • Зачем нужен объект события и как его получить.
  • Как событие плывет в DOM-документе. Где можно поймать и как остановить.
  • Что такое и как предотвратить действие браузера по умолчанию.
  • Как с этим соотносится return false.

В следующих статьях мы разберем, какие свойства есть у объекта события, как их кроссбраузерно обрабатывать и назначать обработчики.