Internet ablakok Android

Bevezető a felugró eseményekhez. JavaScript - Buborékoló események Hogyan működik legtöbbjük

Helló! Ebben az oktatóanyagban olyan fontos fogalomról szeretnék beszélni, mint a buborékolás és az események lehallgatása. A buborékolás olyan jelenség, amikor ha rákattint egy gyermek elemre, az esemény a szülőhöz terjed.

Nagyon hasznos nagy beágyazott listák vagy táblázatok feldolgozásakor, hogy ne rendeljen eseménykezelőt az egyes elemekhez, szülőelemenként egy kezelőt rendelhet hozzá, és az esemény már továbbterjed a szülő összes beágyazott elemére. Vegyünk egy példát.

Ez a kezelő

akkor működik, ha rákattint a mellékelt címkére vagy :

Kattintson az EM gombra, a DIV kezelő aktiválódik

Amint láthatja, amikor rákattint a beágyazott em elemre, a div kezelője elindul. Miért történik? Olvasson tovább, hogy megtudja.

Felület

Tehát a burkolat alapelve:

Bármely esemény esetén nem mindegy, hogy az egér az elem fölé kerül, az esemény először a szülőelemen lesz aktiválva, majd a lánc mentén elterjed az összes beágyazott elemre.

Tegyük fel például, hogy 3 beágyazott FORM> DIV> P elem van, mindegyik eseménykezelővel:

FORMA
DIV

A buborékolás biztosítja, hogy a belső elemre kattint

Először a kattintáskezelőt hívja (ha van ilyen)

Ezt a folyamatot buborékolásnak nevezik, mert úgy tűnik, hogy az események a belső elemtől felfelé „lebegnek” a szüleiken keresztül, akárcsak egy légbuborék a vízben, így megtalálható a buborékolás definíciója is, nos, ez csak az angol buborékolás szóból származik .

A cél elem elérése event.target

Annak érdekében, hogy megtudja, melyik elemről kaptuk ezt vagy azt az eseményt, használja az event.target metódust. (olvassa el az eseményobjektumot).

  • esemény.cél- valójában ez az eredeti elem, amelyen az esemény bekövetkezett.
  • ez- ez mindig az aktuális elem, amelyet felhajtottak, és a kezelő éppen végrehajtja.

Például, ha csak egy form.onclick kezelő van telepítve, akkor az összes űrlapon belüli kattintást elkapja. Ugyanakkor, bárhol is van egy kattintás belül, akkor is felugrik az elemre

, amelyen a kezelő aktiválódik.

Ahol:

  • ez(= event.currentTarget) mindig maga az űrlap lesz, mivel a kezelőt aktiválták rajta.
  • esemény.cél hivatkozást tartalmaz az űrlapon belül egy adott elemre, a leginkább beágyazott elemre, amelyen a kattintás történt.

Ez elvben ugyanaz lehet, mint az event.target, ha az űrlapra kattintanak, és nincs több elem az űrlapon.

Az emelkedés megállítása

Általában az eseménybuborékolás egyenesen a gyökérablak -objektumhoz megy fel.

De meg lehet állítani az emelkedést valamilyen közbenső elemnél.

A buborékolás leállításához hívja az event.stopPropagation () metódust.

Tekintsünk egy példát, amikor a gombra kattint, a body.onclick kezelő nem működik:



Ha egy elemnek több kezelője van beállítva ugyanahhoz az eseményhez, akkor még akkor is, ha a buborékolás megáll, mindegyik végrehajtásra kerül.

Így a stopPropagation megakadályozza az esemény további terjedését, de minden kezelő dolgozik az elemen, de nem tovább a következő elemen.

Az aktuális elem feldolgozásának leállításához a böngészők támogatják az event.stopImmediatePropagation () metódust. Ez a módszer nemcsak megakadályozza a buborékképződést, hanem leállítja az esemény feldolgozását az aktuális elemen.

elmerülés

A szabványban az események "buborékolása" mellett "merítés" is van.

A búvárkodás, ellentétben a felszínre kerüléssel, kevésbé igényes, de ennek ellenére hasznos lesz tudni róla.

Tehát az eseménynek három szakasza van:

  1. Az esemény fentről lefelé halad. Ezt a szakaszt "elfogási szakasznak" nevezik.
  2. Az esemény egy konkrét elemhez érkezett. Ez a "gól szakasz".
  3. Végül is az esemény elkezd felbukkanni. Ez a "felszínre lépési szakasz".

A szabvány ezt a következőképpen mutatja be:

Így, amikor a TD -re kattint, az esemény a szülők láncolatán fog haladni, először lefelé az elemig ("lemerül"), majd felfelé ("felugrik"), az útkezelők segítségével.

Fentebb csak a felemelkedésről írtam, mert valójában más szakaszokat nem használnak, és számunkra észrevétlenek maradnak.

A kezelők semmit nem tudnak az elfogási szakaszról, de az emelkedőn kezdenek dolgozni.

És hogy elfogjon egy eseményt az elfogási szakaszban, csak a következőket kell használnia:

  • Az érvelés igaz, akkor az esemény lefelé menet lehallgatásra kerül.
  • Az érvelés hamis, akkor az esemény buborékolásra fogható.

Példák

A példában ,

,

A kezelők ugyanazok, mint korábban, de ezúttal - a merítési szakaszban. Nos, az elfogás működésének megtekintéséhez kattintson a benne lévő elemre.

A kezelők "felülről lefelé" sorrendben lőnek: FORM → DIV → P.

A JS kód itt a következő:

Var elemek = document.querySelectorAll ("form, div, p"); // akasszon le egy kezelőt minden elemhez az elfogási szakaszban a (var i = 0; i< elems.length; i++) { elems[i].addEventListener("click", highlightThis, true); }


Senki sem zavar, ha mindkét szakaszhoz kezelőket rendel, például:

Var elems = document.querySelectorAll ("form, div, p"); for (var i = 0; i< elems.length; i++) { elems[i].addEventListener("click", highlightThis, true); elems[i].addEventListener("click", highlightThis, false); }

Kattintson a belső elemre

Az esemény sorrendjének megtekintéséhez:
Ez legyen FORM -> DIV -> P -> P -> DIV -> FORM. Vegye figyelembe, hogy az elem

Mindkét szakaszban részt vesz.

Eredmények

  • Amikor egy esemény bekövetkezik - az elem, amelyen az esemény történt, esemény.targetként van megjelölve.
  • Az esemény először a dokumentum gyökeréről az event.target -re lép, és meghívja az addEventListener (…., True) segítségével biztosított kezelőket.
  • Az esemény az event.target elemről a dokumentum elejére kerül, miközben meghívja az addEventListener (…, hamis) kezelőket.

Minden kezelő hozzáférhet az esemény tulajdonságaihoz:

  • event.target a legmélyebb elem, ahol az esemény ténylegesen megtörtént.
  • event.currentTarget (= ez) - az az elem, amelyen az önkezelő pillanatnyilag kiváltotta (amelyhez az esemény "elérte").
  • event.eventPhase - melyik fázisban váltották ki az eseménykezelőt (bemerítés = 1, buborékolás = 3).

A buborékolást meg lehet állítani az event.stopPropagation () metódus meghívásával, de nem ajánlott ezt megtenni, mivel szükség lehet az eseményre a legváratlanabb célokra.

Egy esemény lehallgatása

A nyelv egyik fontos jellemzője az események elfogása. Ha valaki például rákattint egy gombra, akkor az adott gombnak megfelelő onClick eseménykezelőt hívja meg. Az eseménykezelés segítségével biztosíthatja, hogy az ablakának, dokumentumának vagy rétegének megfelelő objektum még azelőtt elfogja és feldolgozza az eseményt, mielőtt az eseménykezelőt a megadott gomb objektuma meghívja erre a célra. Hasonlóképpen, az ablakban, dokumentumban vagy rétegben lévő objektum még azelőtt képes feldolgozni egy eseményjelet, mielőtt eléri a rendeltetési helyét.
Hogy lássuk, mire lehet ez hasznos, nézzük a következő példát:






Kattintson erre a linkre



Mint látható, a címkében nem adunk meg eseménykezelőket ... Ehelyett írunk

window.captureEvents (Event.CLICK);

az esemény lehallgatása érdekében Kattintson ablak objektum. Általában az ablakobjektum nem működik az eseménnyel Kattintson... Az elfogás után azonban átirányítjuk az ablak objektumra. Vegye figyelembe, hogy ben Esemény. KLIKK töredék KLIKK nagybetűvel kell írni. Ha több eseményt szeretne lehallgatni, akkor | szimbólumokkal válassza el őket egymástól. Például:

window.captureEvents (Event.CLICK | Event.MOVE);

Ezenkívül a funkcióban fogantyú () az eseménykezelő szerepkörébe rendeltünk, használjuk az utasítást return true;... Ez valójában azt jelenti, hogy a böngészőnek magát a linket kell feldolgoznia a funkció befejezése után. fogantyú ()... Ha helyette írsz hamis visszatérés; akkor minden véget ér.

Ha most a címkében Ön határozza meg az eseménykezelőt kattintásra, akkor megérti, hogy ezt a programot nem hívják meg, amikor ez az esemény bekövetkezik. Ez nem meglepő, mivel az ablakobjektum még azelőtt elfogja az eseményjelet, mielőtt eléri a linkobjektumot. Ha definiál egy függvényt fogantyú () hogyan

funkció fogantyú (e) (
alert ("Az ablak objektum rögzítette ezt az eseményt!");
window.routeEvent (e);
return true;
}

akkor a számítógép ellenőrzi, hogy nincs -e más eseménykezelő definiálva ehhez az objektumhoz. Az e változó az Event objektumunk, amelyet argumentumként továbbítunk az eseménykezelő függvényhez.

Ezenkívül közvetlenül küldhet eseményjelet egy objektumnak. Ehhez használhatja a módszert handleEvent ()... Ez így néz ki:



Kattintson erre a linkre

onClick = "alert (" Eseménykezelő a második linkhez! ");"> Második link

A kattintási eseményekkel kapcsolatos minden jel a második linken keresztül kerül feldolgozásra - még akkor is, ha egyáltalán nem kattintott egyik linkre sem!

A következő parancsfájl bemutatja, hogyan reagálhat a parancsfájl a billentyűleütésekre. Kattintson egy kulcsra, és nézze meg, hogyan működik ez a szkript.




Напишем небольшой скрипт, с помощью которого добавим обработчик события " click " для всех элементов страницы, а также для объектов document и window .

Hozzon létre egy HTML -oldalt, és illessze be a fenti HTML -kódot. Illesszük be a JavaScript -ben írt szkriptet a záró body -címke elé. Ezt követően nyissa meg az imént létrehozott oldalt egy webböngészőben, nyomja meg az F12 billentyűt, és lépjen a konzolra. Most kattintsunk bal egérgombbal az erős elemhez tartozó területen, és nézzük meg, hogyan buborékol fel az esemény.

Hogyan lehet megszakítani egy esemény buborékolását

Az esemény (buborék) buborékolása megszakítható. Ebben az esetben ez az esemény nem indul el a szülő (szülő) elemeknél. A buborékos esemény (buborék) leállítására tervezett módszert stopPropagation () -nek hívják.

Például változtassuk meg a fenti példánkat, hogy az esemény ne buborékoljon fel a test felett:


Когда пользователь нажимает на кнопку А, событие путешествует таким образом:

Начало
| #document
| Фаза перехвата
| HTML
| BODY
| UL
| LI#li_1
| Кнопка А < - Событие возникает для целевого элемента
| Фаза всплывания
| LI#li_1
| UL
| BODY
| HTML
v #document

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

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

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

В качестве конкретного примера возьмем нашу панель инструментов:

Ul class="toolbar">


  • Теперь, зная, что любое нажатие на кнопке всплывет через элемент ul.toolbar , давайте прикрепим наш обработчик событий на него. К счастью, он у нас уже есть:

    Var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
    Теперь мы имеем намного более чистый код, и мы даже избавились от циклов! Заметьте однако, что мы заменили e.currentTarget на e.target . Причина кроется в том, что мы обрабатываем события на другом уровне.

    e.target - фактическая цель события, то, куда оно пробирается через DOM, и откуда потом будет всплывать.
    e.currentTarget - текущий элемент, который обрабатывает событие. В нашем случае, это ul.toolbar .

    Улучшенные всплывающие события

    В данный момент мы обрабатываем любое нажатие на каждый элемент, которое всплывает через ul.toolbar , но наше условие проверки слишком простое. Что произошло бы, если бы имели более сложный DOM, включащий в себя иконки и элементы, которые не были созданы для того, чтобы по ним кликали?


    Упс! Теперь, когда мы кликаем на li.separator или иконку, мы добавляем ему класс .active . Как минимум, это нехорошо. Нам нужен способ фильтровать события так, чтобы мы реагировали на нужный нам элемент.

    Создадим для этого небольшую функцию-помощника:

    Var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while((el = el.parentNode)); }; };
    Наш помощник делает две вещи. Во-первых, он обходит каждый элемент и его родителей и проверят, удовлетворяют ли они условию, переданному в параметре criteria . Если элемент удовлетворяет - помощник добавляет объекту события поле, называемое delegateTarget , в котором хранится элемент, удовлевторяющий нашим условиям. И затем вызывает обработчик. Соответственно, если ни один элемент не удовлетворяет условию, ни один обработчик не будет вызван.

    Мы можем использовать это так:

    Var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
    То, что доктор прописал: один обработчик событий, прикрепленный к одному элементу, который делает всю работу. Но делает ее только для нужных нам элементов. И он отлично реагирует на добавление и удаление объектов из DOM.

    Подводя итоги

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

    Если бы я хотел сделать из этого библиотеку или использовать код в разработке, я бы добавил пару вещей:

    Функция-помощник для проверки удовлетворения объекта критериям в более унифицированном и функциональном виде. Вроде:

    Var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // Больше критериев };
    Частичное использование помощника так же было бы не лишним:

    Var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
    Оригинал статьи: Understanding Delegated JavaScript Events
    (От переводчика: мой первый, судите строго.)

    Счастливого кодинга!

    Начиналось все с использования JavaScript и классов.

    Однако у меня возникла проблема. Я хотел использовать так называемые Всплывающие События, но также я хотел минимизировать зависимости, которые мне пришлось бы внедрять. Я не хотел подключать библиотеки jQuery для «этого маленького теста», толькло для того, чтобы использовать всплывающие события.

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

    Окей, так в чем проблема?

    Рассмотрим простой пример:

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

    Начнем с HTML:


    Я мог бы использовать стандартный JavaScript обработчик событий вроде такого:

    For(var i = 0; i < buttons.length; i++) { var button = buttons[i]; button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); }
    Выглядит неплохо… Но работать он не будет. По крайней мере, не так, как мы этого ожидаем.

    Замыкания победили

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

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

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

    Что нам нужно, так это отдельный контекст для каждой функции:

    Var buttons = document.querySelectorAll(".toolbar button"); var createToolbarButtonHandler = function(button) { return function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; }; for(var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i])); }
    Намного лучше! А главное, правильно работает. Мы создали функцию createToolbarButtonHandle , которая возвращает обработчик события. Затем для каждой кнопки вешаем свой обработчик.

    Так в чем проблема?

    И выглядит хорошо, и работает. Несмотря на это, мы все еще можем сделать наш код лучше.

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

    Но если мы имеем что-то подобное:

    • // ... еще 997 элементов...

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

    Вместо того чтобы ссылаться на переменную button , чтобы следить, на какую кнопку мы нажали, мы можем использовать event объект (объект «события»), который первым аргументом передается в каждый обработчик события.

    Event объект содержит некоторые данные о событии. В нашем случае нас интересует поле currentTarget . Из него мы получим ссылку на элемент, который был нажат:

    Var toolbarButtonHandler = function(e) { var button = e.currentTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; for(var i = 0; i < buttons.length; i++) { button.addEventListener("click", toolbarButtonHandler); }
    Отлично! Мы не только упростили все до единственной функции, которая используется несколько раз, мы еще и сделали наш код более читаемым, удалив лишнюю функцию-генератор.

    Однако мы все еще можем лучше.

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

    Возможно, существует и другой подход?

    Начнем с того, что разберемся, как же работают события и как они двигаются по нашему DOM.

    Как же большинство из них работает?

    Когда пользователь нажимает на элемент, генерируется событие, чтобы оповестить приложение об этом. Путешествие каждого события происходит в три стадии:
    1. Фаза перехвата
    2. Событие возникает для целевого элемента
    3. Фаза всплывания
    Пометка: не все события проходят стадию перехвата или всплывания, некоторые создаются сразу на элементе. Однако это скорее исключение из правил.

    Событие создается снаружи документа и затем последовательно перемещается по DOM иерархии до target (целевого) элемента. Как только оно добралось до своей цели, событие тем же путем выбирается из DOM элемента.

    Вот наш HTML шаблон:




    Когда пользователь нажимает на кнопку А, событие путешествует таким образом:

    Начало
    | #document
    | Фаза перехвата
    | HTML
    | BODY
    | UL
    | LI#li_1
    | Кнопка А < - Событие возникает для целевого элемента
    | Фаза всплывания
    | LI#li_1
    | UL
    | BODY
    | HTML
    v #document

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

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

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

    В качестве конкретного примера возьмем нашу панель инструментов:

    Ul class="toolbar">


  • Теперь, зная, что любое нажатие на кнопке всплывет через элемент ul.toolbar , давайте прикрепим наш обработчик событий на него. К счастью, он у нас уже есть:

    Var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
    Теперь мы имеем намного более чистый код, и мы даже избавились от циклов! Заметьте однако, что мы заменили e.currentTarget на e.target . Причина кроется в том, что мы обрабатываем события на другом уровне.

    e.target - фактическая цель события, то, куда оно пробирается через DOM, и откуда потом будет всплывать.
    e.currentTarget - текущий элемент, который обрабатывает событие. В нашем случае, это ul.toolbar .

    Улучшенные всплывающие события

    В данный момент мы обрабатываем любое нажатие на каждый элемент, которое всплывает через ul.toolbar , но наше условие проверки слишком простое. Что произошло бы, если бы имели более сложный DOM, включащий в себя иконки и элементы, которые не были созданы для того, чтобы по ним кликали?


    Упс! Теперь, когда мы кликаем на li.separator или иконку, мы добавляем ему класс .active . Как минимум, это нехорошо. Нам нужен способ фильтровать события так, чтобы мы реагировали на нужный нам элемент.

    Создадим для этого небольшую функцию-помощника:

    Var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while((el = el.parentNode)); }; };
    Наш помощник делает две вещи. Во-первых, он обходит каждый элемент и его родителей и проверят, удовлетворяют ли они условию, переданному в параметре criteria . Если элемент удовлетворяет - помощник добавляет объекту события поле, называемое delegateTarget , в котором хранится элемент, удовлевторяющий нашим условиям. И затем вызывает обработчик. Соответственно, если ни один элемент не удовлетворяет условию, ни один обработчик не будет вызван.

    Мы можем использовать это так:

    Var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
    То, что доктор прописал: один обработчик событий, прикрепленный к одному элементу, который делает всю работу. Но делает ее только для нужных нам элементов. И он отлично реагирует на добавление и удаление объектов из DOM.

    Подводя итоги

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

    Если бы я хотел сделать из этого библиотеку или использовать код в разработке, я бы добавил пару вещей:

    Функция-помощник для проверки удовлетворения объекта критериям в более унифицированном и функциональном виде. Вроде:

    Var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // Больше критериев };
    Частичное использование помощника так же было бы не лишним:

    Var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
    Оригинал статьи: Understanding Delegated JavaScript Events
    (От переводчика: мой первый, судите строго.)

    Счастливого кодинга!