Распространение событий (Event Propagation) — это механизм, который определяет, как события распространяются или проходят через DOM-дерево, чтобы достичь своей цели, и что происходит с ней после.

Давайте разберемся с этим на примере. Предположим, вы назначили обработчик события click для гиперссылки (элемент <a>), которая вложена в абзац (элемент <p>). Теперь, если вы нажмете на эту ссылку, обработчик будет выполнен.

Но, если вы назначите обработчик события click для абзаца, содержащего ссылку, то даже в этом случае нажатие на ссылку все равно вызовет обработчик. Это происходит потому, что события не просто влияют на целевой элемент, они перемещаются по DOM-дереву, чтобы достичь своей цели. Дословный перевод этого явления — распространение событий (Event Propagation).

В современном браузере распространение событий происходит в две фазы: захват (capturing) и фаза «пузырьков» (bubbling). Прежде чем мы продолжим, взгляните на следующую иллюстрацию:

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

Концепция распространения событий была введена для того, чтобы справляться с ситуациями, когда несколько элементов в иерархии DOM с отношениями родитель-потомок имеют обработчики событий для одного и того же события, например, щелчок мыши. Теперь вопрос заключается в том, какое событие click элемента будет обрабатываться первым, когда пользователь нажимает на внутренний элемент — событие click внешнего элемента или внутреннего элемента. Рассмотрим этот вопрос подробнее.

Формально есть 3 фазы: захват (capture), цель (target) и пузырьковая фаза (bubble phase). Но 2-я фаза не обрабатывается отдельно в современных браузерах, обработчики, зарегистрированные как для фаз захвата, так и для пузырьковых операций, выполняются на этой фазе.

Фаза захвата (capture)

На этапе захвата события распространяются из окна вниз по дереву DOM на целевой узел. Например, если пользователь щелкает гиперссылку, это событие щелчка будет проходить через элемент <html>, <body> до элемента <p>, содержащего ссылку.

Также, если какой-либо предок целевого элемента и сама цель имеют специально зарегистрированный получатель событий захвата (capturing event listener) для этого типа события, слушатели (event listener) выполняются на этом этапе. Давайте посмотрим на следующий пример:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Capturing Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">
    <p class="hint">
        <a href="#">A</a>
    </p>
</div>

<script>
    function showTagName() {
        alert("Capturing: "+ this.tagName);
    }
    
    var elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showTagName, true);
    }
</script>
</body>
</html><!DOCTYPE html>

Захват событий поддерживается не во всех браузерах и используется редко. Например, Internet Explorer до версии 9.0 не поддерживает захват событий.

Кроме того, захват событий работает только с обработчиками событий, зарегистрированными методом addEventListener(), когда для третьего аргумента установлено значение true. Традиционный метод назначения обработчиков событий, такой как использование onclick, onmouseover и т. д. здесь не работает. Подробнее см. Руководство по Event Listeners в JavaScript.

Фаза «пузырей» (bubbling)

В фазе bubbling происходит полная противоположность. В этой фазе событие распространяется или всплывает вверх по дереву DOM, от целевого элемента до последнего из предков целевого элемента. Например, если пользователь щелкает гиперссылку, это событие щелчка будет проходить через элемент <p>, содержащий ссылку, элемент <body> и узел документа <html>.

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Bubbling Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div onclick="alert('Bubbling: ' + this.tagName)">DIV
    <p onclick="alert('Bubbling: ' + this.tagName)">P
        <a href="#" onclick="alert('Bubbling: ' + this.tagName)">A</a>
    </p>
</div>
</body>
</html>

События bubbling поддерживаются во всех браузерах и работают для всех обработчиков, независимо от того, как они зарегистрированы, например. используя onclick или addEventListener() (если только они не зарегистрированы в качестве прослушивателя событий захвата). Вот почему термин «распространение событий» (capturing event listener) часто используется как синоним всплытия событий (bubbling).

Доступ к целевому элементу (target)

Целевым элементом является узел DOM, который сгенерировал событие. Например, если пользователь щелкает гиперссылку, целевым элементом является гиперссылка.

Целевой элемент доступен как event.target; доступ не изменяется на всех этапах распространения события. Кроме того, ключевое слово this представляет текущий элемент (то есть элемент, к которому прикреплен работающий в данный момент обработчик). Давайте посмотрим на пример:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Target Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;			
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">
    <p class="hint">
        <a href="#">A</a>
    </p>
</div>

<script>
    // Выбираем элемент div
    var div = document.getElementById("wrap");

    // Присоединяем обработчик событий onclick
    div.onclick = function(event) {
        event.target.style.backgroundColor = "lightblue";

        // Позволяем браузеру завершить рендеринг цвета фона, прежде чем показать предупреждение
        setTimeout(() => {
            alert("target = " + event.target.tagName + ", this = " + this.tagName);
            event.target.style.backgroundColor = ''
        }, 0);
    }
</script>
</body> 
</html>

У знака стрелки (=>), который мы использовали в приведенном выше примере, более короткий синтаксис, чем у выражения функции и это заставит ключевое слово this вести себя правильно. Пожалуйста, ознакомьтесь с Руководством по функциям ES6 в JavaScript, чтобы узнать больше об этой функции.

Остановка распространения события

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

Например, предположим, что у вас есть вложенные элементы, и каждый элемент имеет обработчик события onclick, который отображает диалоговое окно предупреждения. Обычно, когда вы щелкаете по внутреннему элементу, все обработчики будут выполняться одновременно, так как событие «всплывает» по DOM-дереву.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showAlert() {
        alert("You clicked: "+ this.tagName);
    }

    var elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showAlert);
    }
</script>
</body>
</html>

Чтобы предотвратить эту ситуацию, вы можете остановить всплытие события в DOM-дереве, используя метод event.stopPropagation(). В следующем примере прослушиватель события (event listener) click для родительских элементов не будет выполняться, если щелкнуть дочерние элементы.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Stop Event Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showAlert(event) {
        alert("You clicked: "+ this.tagName);
        event.stopPropagation();
    }

    var elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showAlert);
    }
</script>
</body>
</html>

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

В следующем примере мы прикрепили несколько прослушивателей к гиперссылке, но выполняться будет только один прослушиватель при нажатии на ссылку и вы увидите только одно предупреждение.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Stop Immediate Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div onclick="alert('You clicked: ' + this.tagName)">DIV
    <p onclick="alert('You clicked: ' + this.tagName)">P
        <a href="#" id="link">A</a>
    </p>
</div>

<script>
    function sayHi() {
        alert("Hi, there!");
        event.stopImmediatePropagation();
    }
    function sayHello() {
        alert("Hello World!");
    }
    
    // Присоединяем нескольких обработчиков событий к гиперссылке
    var link = document.getElementById("link");
    link.addEventListener("click", sayHi);  
    link.addEventListener("click", sayHello);
</script>
</body>
</html>

Если несколько слушателей присоединены к одному и тому же элементу для одного и того же типа события, они выполняются в порядке, в котором они были добавлены. Но если какой-либо слушатель вызывает метод event.stopImmediatePropagation(), остальные слушатели выполняться не будут.

Предотвращение действия по умолчанию

С некоторыми событиями связано действие по умолчанию. Например, если вы щелкнете по ссылке, браузер переместит вас к цели ссылки, когда вы нажмете на кнопку отправки формы, браузер отправит форму и т. д. Такие действия по умолчанию можно предотвратить с помощью метода preventDefault() объекта события.

Однако предотвращение действий по умолчанию не останавливает распространение событий; событие продолжает распространяться на DOM-дерево как обычно. Вот пример:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Prevent Default Demo</title>
</head>
<body>
<form action="/examples/html/action.php" method="post" id="users">
    <label>First Name:</label>
    <input type="text" name="first-name" id="firstName">
    <input type="submit" value="Submit" id="submitBtn">
</form>

<script>
    var btn = document.getElementById("submitBtn");
    
    btn.addEventListener("click", function(event) {
        var name = document.getElementById("firstName").value;
        alert("Sorry, " + name + ". The preventDefault() won't let you submit this form!");
        event.preventDefault(); // Запрещаем отправку формы
    });
</script>
</body>
</html>

Похожие посты

Руководство по сортировке массивов в JavaScript

Сортировка — обычная задача при работе с массивами. Она будет использоваться, например, если вы захотите отобразить названия городов или регионов в алфавитном порядке. JavaScript массив (array) имеет встроенный метод sort() для сортировки элементов массива в алфавитном порядке. Следующий пример демонстрирует, как это работает: Реверсирование массива Вы можете использовать метод reverse(), чтобы изменить порядок элементов массива…

Руководство по массивам в JavaScript

Массивы — это сложные переменные, которые позволяют нам хранить группы значений под одним именем переменной. Массивы JavaScript могут хранить любое допустимое значение, включая строки, числа, объекты, функции и даже другие массивы, что позволяет создавать более сложные структуры данных, такие как массив объектов или массив массивов. Предположим, вы хотите сохранить название цветов в своем коде JavaScript….

Руководство по работе с атрибутами DOM в JavaScript

Атрибуты — это специальные слова, используемые внутри начального тега HTML-элемента для управления поведением тега или предоставления дополнительной информации о теге. JavaScript предоставляет несколько методов для добавления, удаления или изменения атрибутов HTML-элемента. В этом разделе мы узнаем об этих методах подробно. Получение значения атрибута элемента Метод getAttribute() используется для получения текущего значения атрибута элемента. Если указанный…

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 5 / 5. Количество оценок: 1

Оценок пока нет. Поставьте оценку первым.