Конспект JS-course

Введение в браузерные события

Источник: http://learn.javascript.ru/introduction-browser-events

Для реакции на действия посетителя и внутреннего взаимодействия скриптов существуют события.

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

Существует много видов событий.

  • DOM-события, которые инициализируются элементами DOM. Например:
    • Событие click происходит, когда кликнули на элемент;
    • Событие mouseover — когда на элемент наводится мышь;
    • Событие focus — когда посетитель фокусируется на элементе;
    • Событие keydown — когда посетитель нажимает клавишу.
  • События для окна браузера. Например, resize — когда изменяется размер окна.
  • Есть загрузки файла/документа: load, readystatechange, DOMContentLoaded

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

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

Есть несколько способов назначить событию обработчик. Сейчас мы их рассмотрим, начиная от самого простого.

Использование атрибута HTML

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

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

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

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

В действии:

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

Запись вида onclick=&quot;alert(&quot;Клик&quot;)&quot; не будет работать. Если вам действительно нужно использовать именно двойные кавычки, то это можно сделать, заменив их на &amp;quot;: onclick=&quot;alert(&amp;quot;Клик&amp;quot;)&quot;.

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

Следующий пример по клику запускает функцию countRabbits().

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">

  <script>
    function countRabbits() {
      for(var i = 1; i <= 3; i++) {
        alert("Кролик номер " + i);
      }
    }
  </script>
</head>
<body>
  <input type="button" onclick="countRabbits()" value="Считать кроликов!"/>
</body>
</html>

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

Использование свойства DOM-объекта

Можно назначать обработчик, используя свойство DOM-элемента on<событие>.

Пример установки обработчика click элементу с id=&quot;myElement&quot;:

<input id="myElement" type="button" value="Нажми меня"/>
<script>
var elem = document.getElementById('myElement');

elem.onclick = function() {
    alert('Спасибо');
}
</script>

В действии:

Если обработчик задан через атрибут, то соответствующее свойство появится у элемента автоматически. Браузер читает HTML-разметку, создаёт новую функцию из содержимого атрибута и записывает в свойство onclick.

Первичным является именно свойство, а атрибут — лишь способ его инициализации.

Эти два примера кода работают одинаково:

  • Только HTML:
      <input type="button" onclick="alert('Клик!')" value="Кнопка"/>
    
  • HTML + JS:
      <input type="button" id="button" value="Кнопка"/>
      <script>
        document.getElementById('button').onclick = function() {
            alert('Клик!');
        }
      </script>
    

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

В примере ниже, назначение через JavaScript перезапишет обработчик из атрибута:

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

<script>
    var elem = document.getElementsByTagName('input')[0];

    elem.onclick = function() { // перезапишет существующий обработчик
        alert('После');
    }
</script>

Обработчиком можно назначить уже существующую функцию:

function sayThanks() {
    alert('Спасибо!');
}

document.getElementById('button').onclick = sayThanks;

Частые ошибки

  • Функция должна быть присвоена как sayThanks, а не sayThanks():
document.getElementById('button').onclick = sayThanks;

Если добавить скобки, то sayThanks() — будет уже результат выполнения функции (а так как в ней нет return, то в onclick попадёт undefined). Нам же нужна именно функция.

А вот в разметке как раз скобки нужны:

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

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

document.getElementById('button').onclick = function() {
  sayThanks();  // содержимое атрибута
}
  • Используйте свойство, а не атрибут. Так неверно: elem.setAttribute(&#39;onclick&#39;, func).

Хотя, с другой стороны, если func — строка, то такое присвоение будет успешным, например:

// сработает, будет при клике выдавать 1
document.body.setAttribute('onclick', 'alert(1)');

Браузер в этом случае сделает функцию-обработчик с телом из строки alert(1).

…А вот если func — не строка, а функция (как и должно быть), то работать совсем не будет:

// при нажатии на body будут ошибки
document.body.setAttribute('onclick', function() { alert(1) });

Значением атрибута может быть только строка. Любое другое значение преобразуется в строку. Функция в строчном виде обычно даёт свой код: &quot;function() { alert(1) }&quot;.

  • Используйте функции, а не строки.

Запись elem.onclick = &#39;alert(1)&#39; будет работать, но не рекомендуется.

При использовании в такой функции-строке переменных из замыкания будут проблемы с JavaScript-минификаторами. Здесь мы не будем вдаваться в детали этих проблем, но общий принцип такой — функция должна быть function.

  • Названия свойств регистрозависимы, поэтому on&lt;событие&gt; должно быть написано в нижнем регистре.

Свойство ONCLICK работать не будет.

Доступ к элементу, this

Внутри обработчика события this ссылается на текущий элемент. Это можно использовать, чтобы получить свойства или изменить элемент.

В коде ниже button выводит свое содержимое, используя this.innerHTML:

<button onclick="alert(this.innerHTML)">Нажми меня</button>

В действии:

Недостатки назначения через onсобытие

Фундаментальный недостаток описанных способов назначения обработчика — невозможность повесить несколько обработчиков на одно событие.

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

При этом новый обработчик будет затирать предыдущий. Например, следующий код на самом деле назначает один обработчик — последний:

input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // заменит предыдущий обработчик

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

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

Для назначения обработчиков существуют специальные методы. Как правило, в браузерах они стандартные, кроме IE<9, где они похожи, но немного другие.

Методы IE<9

Сначала посмотрим метод для старых IE, т.к. оно чуть проще.

Назначение обработчика осуществляется вызовом attachEvent:

element.attachEvent( "on"+event, handler);

Удаление обработчика — вызовом detachEvent:

element.detachEvent( "on"+event, handler);

Например:

var input = document.getElementById('button')
function handler() {
    alert('спасибо!')
}
input.attachEvent( "onclick" , handler) // Назначение обработчика
// ....
input.detachEvent( "onclick", handler) // Удаление обработчика

Обычно, обработчики ставятся. Но бывают ситуации, когда их нужно удалять или менять.

В этом случае нужно передать в метод удаления именно функцию-обработчик. Такой вызов будет неправильным:

input.attachEvent( "onclick" ,
   function() {alert('Спасибо!')}
)
// ....
input.detachEvent( "onclick",
   function() {alert('Спасибо!')}
)

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

Пример ниже будет работать только в IE и Opera:

<input id="myElement" type="button" value="Нажми меня"/>

<script>
  var myElement = document.getElementById("myElement")
  var handler = function() {
    alert('Спасибо!')
  }

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

  myElement.attachEvent("onclick", handler); // первый
  myElement.attachEvent("onclick", handler2); // второй
</script>

У обработчиков, назначенных с attachEvent, нет this. Это важная особенность и подводный камень старых IE.

Назначение обработчиков по стандарту

Официальный способ назначения обработчиков из стандарта W3C работает во всех современных браузерах, включая IE9+.

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

element.addEventListener(event, handler, phase);

Удаление:

element.removeEventListener(event, handler, phase);

Как видите, похоже на attachEvent/detachEvent, только название события пишется без префикса «on».

Еще одно отличие от синтаксиса Microsoft — это третий параметр: phase, который обычно не используется и выставлен в false. Позже мы посмотрим, что он означает.

Использование этого метода — такое же, как и у attachEvent:

function handler() { ... }

elem.addEventListener( "click" , handler, false) // назначение обработчика

elem.removeEventListener( "click", handler, false) // удаление обработчика

Особенности специальных методов

  • Можно поставить столько обработчиков, сколько вам нужно.
  • Нельзя получить все назначенные обработчики из элемента.
  • Браузер не гарантирует сохранение порядка выполнения обработчиков. Они могут быть назначены в одном порядке, а выполниться — в другом.
  • Кроссбраузерные несовместимости.

Кроссбраузерный способ назначения обработчиков

Можно объединить способы для IE<9 и современных браузеров, создав свои методы addEvent(elem, type, handler) и removeEvent(elem, type, handler):

var addEvent, removeEvent;

if (document.addEventListener) { // проверка существования метода
  addEvent = function(elem, type, handler) {
    elem.addEventListener(type, handler, false);
  };
  removeEvent = function(elem, type, handler) {
    elem.removeEventListener(type, handler, false);
  };
} else {
  addEvent = function(elem, type, handler) {
    elem.attachEvent("on" + type, handler);
  };
  removeEvent = function(elem, type, handler) {
    elem.detachEvent("on" + type, handler);
  };
}

...
// использование:
addEvent(elem, "click", function() { alert("Привет"); });

Это хорошо работает в большинстве случаев, но у обработчика не будет this в IE, потому что attachEvent не поддерживает this.

Кроме того, в IE<8 есть проблемы с утечками памяти… Но если вам не нужно this, и вы не боитесь утечек (как вариант — не поддерживаете IE<8), то это решение может подойти.

Итого

Есть три способа назначения обработчиков событий:

  1. Атрибут HTML: onclick=&quot;...&quot;.
  2. Свойство: elem.onclick = function.
  3. Специальные методы:
    • Для IE<9: elem.attachEvent(on+событие, handler) (удаление через detachEvent).
    • Для остальных: elem.addEventListener(событие, handler, false) (удаление через removeEventListener).

Все способы, кроме attachEvent, обеспечивают доступ к элементу, на котором сработал обработчик, через this.