Статьи

События JavaScript: с нуля

Предполагается, что почти все, что вы делаете в JavaScript, начинается тогда, когда что-то происходит: пользователь нажимает кнопку, наводит курсор мыши на меню, нажимает клавишу, и вы получаете идею. Это довольно просто сделать с библиотеками, такими как jQuery, но знаете ли вы, как связывать события в сыром JavaScript? Сегодня я собираюсь научить вас именно этому!


Обработка событий JavaScript не является ракетостроением. Конечно, есть различия в браузере (что вы ожидали?), Но как только вы освоите его и создадите свои собственные вспомогательные функции даже для игрового поля, вы будете рады, что знаете это. Даже если вы все еще планируете использовать библиотеку для такого рода вещей, хорошо знать, что происходит под поверхностью. Вот так.


Самая базовая обработка событий в JavaScript — это обработка событий уровня 0. Это довольно просто: просто назначьте свою функцию свойству события элемента. Заметим:

1
2
var element = document.getElementById(‘myElement’);
element.onclick = function () { alert(‘your function here’);

Как только у меня есть элемент, я устанавливаю свойство onclick равным функции. Теперь при нажатии на element эта функция будет запущена, и мы увидим окно предупреждения.
Но как я узнал, как использовать onclick ? Вы можете получить список доступных событий в Википедии . Прелесть метода Level 0 в том, что он поддерживается во всех браузерах, широко используемых сегодня. Недостатком является то, что вы можете зарегистрировать каждое событие только один раз для каждого элемента. Так что это вызовет только одно предупреждение:

1
2
element.onclick = function () { alert(‘function 1’);
element.onclick = function () { alert(‘I override function 1’) };

Только вторая функция будет запущена при нажатии элемента, потому что мы переназначили свойство onclick. Чтобы удалить обработчик события, мы можем просто установить его в null.

1
element.onclick = null;

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



Теперь, когда вы знакомы с событиями JavaScript, давайте обсудим два важных аспекта, прежде чем перейти к более совершенным методам: объект события и ключевое слово this . Когда событие выполняется, появляется много информации о событии, которую вы, возможно, захотите узнать: какая клавиша была нажата? какой элемент был нажат? пользователь щелкнул левой, правой или средней кнопкой? Все эти данные и многое другое хранится в объекте события. Для всех браузеров, кроме Internet Explorer, вы можете получить этот объект, передав его в качестве параметра функции обработки. В IE вы можете получить его из глобальной переменной window.event . Нетрудно устранить эту разницу.

1
2
3
4
5
function myFunction (evt) {
    evt = evt ||
    /* .
}
element.onclick = myFunction;

myFunction принимает параметр события для всех хороших браузеров, и мы переназначаем его в window.event если window.event не существует. Мы рассмотрим некоторые свойства объекта события чуть позже.

Эта ключевая работа важна в функциях обработки событий; это относится к элементу, который получил событие.

1
2
3
4
element.onclick = function () {
    this.style.backgroundColor = ‘red’;
    //same as element.style.backgroundColor = ‘red’;
}

К сожалению, в Internet Explorer this не относится к действующему элементу; это относится к объекту окна. Это бесполезно, но мы рассмотрим способ исправить это немного.


Обработка событий уровня 2 — это спецификация W3C для событий DOM. Это довольно просто, на самом деле.

1
element.addEventListener(‘mouseover’, myFunction, false);

Метод называется addEventListener . Первый параметр — это тип события, которое мы хотим прослушать. Обычно это то же имя, что и события уровня 0, без префикса «on». Второй параметр — это либо имя функции, либо сама функция. Последний параметр определяет, захватывать мы или нет; мы обсудим это через минуту.
Одним из больших преимуществ использования addEventListener является то, что теперь мы можем назначить несколько событий для одного события на один элемент:

1
2
element.addEventListener(‘dblclick’, logSuccessToConsole, false);
element.addEventListener(‘dblclick’, function () { console.log(‘this function doens\’t override the first.’); }, false);

Чтобы удалить эти обработчики, используйте функцию removeEventListener . Итак, чтобы удалить функцию, которую мы добавили выше, мы делаем это:

1
element.removeEventListener(‘dblclick’, logSuccessToConsole, false);

Последний параметр в addEventListner и removeEventListener является логическим значением, определяющим, removeEventListener ли мы событие на этапе захвата или всплытия. Вот что это значит:
Когда вы щелкаете по элементу, вы также щелкаете по каждому из его элементов-предков.

1
2
3
4
5
6
7
<body>
    <div id=»wrapper»>
        <div id=»toolbar»>
            <span id=»save»>Save Work
        </div>
    </div>
</body>

Если я щелкну на span#save , я также нажму div#toolbar div#wrapper , div#wrapper и body . Так что, если у любого из этих элементов есть прослушиватели событий, прослушивающие щелчки, будут вызваны их соответствующие функции. Но в каком порядке они должны быть вызваны? Вот что решает последний параметр. В спецификации W3C наше событие click будет начинаться с body и двигаться вниз по цепочке, пока не достигнет span#save ; это этап захвата. Затем он идет от span#save до body ; это пузырчатая стадия. Это легко запомнить, потому что пузыри всплывают вверх. Не все события пузырьков; эта таблица Википедии скажет вам, какие из них делают, а какие нет.

Таким образом, если для параметра захвата установлено значение true , событие будет инициировано по пути вниз; если установлено значение false , оно будет срабатывать по пути вверх.

Возможно, вы заметили, что до сих пор во всех моих примерах для захвата было задано значение false, поэтому событие запускается на стадии пузыривания. Это связано с тем, что Internet Explorer не имеет фазы захвата. В IE события только пузырьковые.


IE имеет свой собственный способ работы с событиями. Он использует attachEvent .

1
element.attachEvent(‘onclick’, runThisFunction);

Поскольку события IE только пузырьковые, вам не нужна третья переменная. Первые два аналогичны addEventListener , за исключением того, что IE требует префикс «on» для типа события. Конечно, с помощью этой модели вы можете назначить несколько событий одного вида отдельному элементу. Чтобы удалить события, используйте detachEvent .

1
element.detachEvent(‘onclick’, runThisFunction);

Что если ваши элементы не хотят, чтобы их родители узнали об их событиях? Это не трудно скрыть. В IE вы используете свойство cancelBubble объекта события, который передается вызываемой функции. В остальном вы можете использовать метод stopPropogation , также для объекта события.

1
2
e.cancelBubble = true;
if (e.stopPropagation) { e.stopPropagation();

Метод stopPropogation также останавливает события на своих дорожках во время этапа захвата.


Я готов поспорить, что большинство свойств объекта события останутся неиспользованными, но есть несколько избранных, которые вы найдете бесценными. Давайте посмотрим на тех немногих.


Свойство target — это самый глубокий элемент, по которому щелкнули. На изображении выше вы можете видеть, что я нажал h1#button . В IE это свойство называется srcElement ; чтобы добраться до этого свойства, сделайте что-то вроде этого:

1
var target = e.target ||

Свойство currentTarget — это элемент, который имеет событие прямо сейчас. На этом снимке экрана currentTarget — это div#wrapper . Это означает, что я нажал h1#button , но объект события был зарегистрирован в консоли в функции, вызываемой слушателем событий div#wrapper .

Но это приведет вас к циклу: IE не имеет свойства currentTarget или чего-либо подобного. Вспоминая тот факт, что IE даже не позволяет this равняться элементу, инициирующему событие, это означает, что у нас нет способа получить элемент, который вызвал событие, когда пузырится (это означает, что потомок элемента был нажат). Смущенный? Может быть, пример поможет: если в Internet Explorer у нас есть этот HTML. , ,

1
2
3
<div id=»parent»>
    <span id=»child»>Button
</div>

, , , и этот обработчик событий. , ,

1
2
var par = document.getElementById(‘parent’);
parent.attachEvent(‘onclick’, doSomething);

, , , затем, когда мы щелкаем по span#child и событие всплывает до div#parent , у нас нет никакой возможности изнутри функции doSomething получить элемент (в данном случае div#parent ), на котором было инициировано событие.

Существуют и другие свойства объекта события, которые вы найдете полезными, в зависимости от случая. На событии клавиатуры ( keyCode charCode ) вы будете использовать keyCode и charCode . Вы сможете определить, удерживал ли пользователь клавишу alt, shift или ctrl (cmd) при нажатии / нажатии. Исследуйте объекты событий различных событий и посмотрите, с чем вам нужно работать!


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

  • IE использует другую модель, чем другие браузеры.
  • IE не имеет доступа к родительскому объекту события; ему не хватает свойства currentTarget , а объект this ссылается на объект окна.

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

Начнем с addEvent ; вот первый раунд:

1
2
3
4
5
6
7
8
var addEvent = function (element, type, fn) {
    if (element.attachEvent) {
        element.attachEvent(‘on’ + type, fn);
    }
    else {
        element.addEventListener(type, fn, false);
    }
}

Я назначаю анонимную функцию переменной addEvent по причинам, которые вы увидите через минуту. Все, что нам нужно сделать, это проверить, есть ли у элемента метод attachEvent . Если это произойдет, мы в IE, и мы будем использовать этот метод. Если нет, мы можем использовать addEventListener .

Однако это не исправляет тот факт, что у нас нет возможности получить доступ к родительскому элементу события в IE. Для этого мы сделаем функцию атрибутом элемента; таким образом, это воспринимается как метод элемента, и this будет равняться элементу. Да, я знаю, великолепно, не так ли? Ну, я не могу взять кредит: эта идея взята из сообщения в блоге Джона Резига .

01
02
03
04
05
06
07
08
09
10
var addEvent = function (element, type, fn) {
    if (element.attachEvent) {
        element[‘e’+type+fn] = fn;
        element[type+fn] = function () { element[‘e’+type+fn](window.event) };
        element.attachEvent(‘on’ + type, element[type+fn]);
    }
    else {
        element.addEventListener(type, fn, false);
    }
}

Первая дополнительная строка ( element['e'+type+fn] = fn; ) делает функцию методом элемента, так что теперь он будет равен элементу. Вторая строка — это функция, которая выполняет метод, передавая объект window.event. Это избавит вас от дополнительного шага проверки параметра объекта события в вашей функции. Наконец, обратите внимание, что мы изменили параметр функции в методе attachEvent на element[type+fn] .

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
var addEvent = function(el, type, fn) {
    if(el.attachEvent) {
        addEvent = function (el, type, fn) {
            el[‘e’+type+fn] = fn;
            el[type+fn] = function () { el[‘e’+type+fn](window.event);
            el.attachEvent(‘on’ + type, el[type+fn]);
        }
    }
    else {
        addEvent = function (el, type, fn) {
            el.addEventListener(type, fn, false);
        }
    }
    addEvent(el, type, fn);
}

Теперь, если метод addEvent найден, мы переписываем addEvent чтобы просто включить части IE; если нет, мы просто включим хорошие части браузера.
Наша функция removeEvent будет очень похожа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
var removeEvent = function (element, type, fn) {
    if(element.detachEvent) {
        removeEvent = function (element, type, fn) {
            element.detachEvent(‘on’ + type, el[type+fn]);
            element[type+fn] = null;
        }
    }
    else {
        removeEvent = function (element, type, fn) {
            element.removeEventListener(type, fn, false);
        }
    }
    removeEvent(element, type, fn);
}

Вот и все для наших кросс-браузерных функций событий. Но есть одна вещь, которая мне не нравится в них: функции не являются методами элементов; вместо этого элемент является параметром. Я знаю, это просто вопрос стиля, но мне нравится

1
btn.addEvent(‘click’, doThis);

лучше чем

1
addEvent(btn, ‘click’, doThis);

Мы могли бы сделать это, обернув наши функции в это:

1
2
3
4
5
6
7
var E = Element.prototype;
E.addEvent = function(type, fn) {
    addEvent(this, type, fn);
}
E.removeEvent = function (type, fn) {
    removeEvent(this, type, fn);
}

Это будет работать во всех современных браузерах, а также в IE8; IE7 и IE6 подавились Element . Это зависит от вашей аудитории; возьми это или оставь!


Теперь, когда вы знаете все о событиях JavaScript, не спешите и наполните свой следующий проект слушателями событий. Если у вас есть много элементов, реагирующих на один и тот же тип события (скажем, щелчок), разумнее воспользоваться всплывающей подсказкой. Вы можете просто добавить прослушиватель событий к элементу-предку, даже к телу, и использовать свойство target / srcElement объекта события, чтобы выяснить, какой более глубокий элемент был выбран. Это называется делегированием событий. Вот бесполезный пример:

Вот немного HTML:

1
2
3
4
5
6
<ul id=»nav»>
    <li id=»home»>home</li>
    <li id=»portfolio»>portfolio</li>
    <li id=»about»>about</li>
    <li id=»contact»>contact</li>
</ul>

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
function navigation(e) {
    var el = e.target ||
    switch(el.id) {
        case ‘home’:
            alert(‘go home’);
            break;
        case ‘portfolio’:
            alert(‘check out my products’);
            break;
        case ‘about’:
            alert(‘all those disgusting details’);
            break;
        case ‘contact’:
            alert(‘I\’m flattered you\’d like to talk’);
            break;
        default:
            alert(‘how did you get here?’);
    }
}
var nav = document.getElementById(‘nav’);
addEvent(nav, ‘click’, navigation);

Мы используем оператор switch / case для просмотра идентификатора элемента, который щелкает глубже. Затем мы делаем что-то соответственно.


Так что теперь вы можете использовать события JavaScript с лучшими из них. Веселитесь с ним и задавайте любые вопросы в комментариях!