事件冒泡 event bubbling DOM Event javascript event

網頁中的事件氣泡傳遞

陳乾正 2017/12/25 02:51:20
1470

網頁中的事件氣泡傳遞


簡介

在前端網頁程式設計中元素動作的事件處理有一個特別的設計,就是事件會像泡泡一樣向上傳遞,想要更加瞭解請繼續看下去。

作者

陳乾正


1.事件冒泡

在前端網頁程式設計中元素動作的事件處理有一個特別的設計,就是事件會像泡泡一樣,由引發事件的起源點向網頁的根節點一層一層的向上傳遞,這個動作稱為事件冒泡(event bubbling)

 

假設有一網頁HTML原始碼如下:

 <!DOCTYPE html>

<html>

<body>

<div id="content" style="width:600px; background-color: #CCEEFF; padding: 20px">

    <span>content box</span>

    <div id="handler" style="background-color: #99FF99; padding: 20px">

        <span>handler box</span>

        <table id="datatable" style="width:100%; background-color: #FFFFBB;">

            <thead>

                <tr>

                    <th>Id</th>

                    <th>Name</th>

                    <th>&nbsp;</th>

                </tr>

            </thead>

            <tbody>

                <tr data-id="A001">

                    <td>A001</td>

                    <td>Mary</td>

                    <td><input type="button" value="click"></td>

                </tr>

                <tr data-id="A002">

                    <td>A002</td>

                    <td>Jone</td>

                    <td><input type="button" value="click"></td>

                </tr>

                <tr data-id="A003">

                    <td>A003/td>

                    <td>LiLi</td>

                    <td><input type="button" value="click"></td>

                </tr>

            </tbody>

        </table>

    </div>

</div>

 

<div id="messagewall" style="width:600px; border: 1px solid; background-color: #f0f0f0; margin-top: 20px">

</div>

</body>

</html>

網頁執行結果顯示如下:
頁面上共有二個div區塊容器,分別為contenthandler及一個table datatable其階層關係為content > handler > datatable

 

接著為頁面上的元素綁定事件處理函式如下,當元素發生click事件時,在div messagewall內容顯示相對應文字。

<script>
$(function(){
    $(window).on('click',
    function (e) {
        addmsg('> window click.');
    });
 
    $(document).on('click',
    function (e) {
        addmsg('=> document click.');
    });
 
    $('body').on('click',
    function (e) {
        addmsg('==> body click.');
    });
 
    $('#content').on('click',
    function (e) {
        addmsg('===> content box click.');
    });
    
    $('#handler').on('click',
    function (e) {
        addmsg('====> handler box click.');
    });
 
    $('#datatable').on('click',
    function (e) {
        addmsg('=====> datatable click.');
    });
 
    $('#datatable tr').on('click',
    function (e) {
        addmsg('======> datatable tr click.');
    });
 
    $('#datatable td').on('click',
    function (e) {
        addmsg('=======> datatable td click.');
    });
    
    $('#datatable tr input[type="button"]').on('click',
    function (e) {
        addmsg('========> button click.');
    });
});
 
function addmsg(msg) {
    $('<div>').html(msg).appendTo('#messagewall');
}
</script>
當我們點繫頁面表格第一列A001click button時,會得到結果如下,顯示並不是只有button觸發click單一事件執行完成後就結束,而是會將被觸發的事件event object逐層的往上傳送,經過table tdtrtable…各個節點並嘗試觸發該節點的相同事件,若有綁定相同事件則執行相對應的事件處理程式,若沒有則繼續傳送直到網頁DOM最上層根節點為止,有如水中泡泡的上浮一般,稱之為事件冒泡。

 

========> button click.
=======> datatable td click.
======> datatable tr click.
=====> datatable click.
====> handler box click.
===> content box click.
==> body click.
=> document click.
> window click
將網頁內容轉為DOM樹狀結構及橫向及事件傳遞表示如下:

2.事件委派

那我們可以利用事件氣泡的特性做什麼事呢? 再來看一個範例,將table最後欄位修改顯示二個buttonokcancel程式碼及呈現畫面如下
<td>
    <input type="button" value="ok">
    <input type="button" value="cancel">
</td>
接下來修改事件綁定只在handler div加上click事件處理程式如下,當補捉到click事件時首先取得原始觸發事件的元素(event.target)並判斷元素的值若為ok則呼叫執行ok()函式,若值為cancel則呼叫執行cancel()函式。
$(function(){
    $('#handler').on('click',
    function (event) {
        let target = event.target;
        if(target.value == 'ok') {
            ok(target);
        }
        else if(target.value == 'cancel') {
            cancel(target);
        }
    });
});
 
function ok(target) {
        addmsg('=> ' + $(target).closest('tr').data('id') + ' ok button click.');
}
 
function cancel(target) {
        addmsg('=> ' + $(target).closest('tr').data('id') + ' cancel button click.');
}
此時若分別點擊了表格第一列A001ok button第二列A002cancel button第三列A003ok button則結果顯示文字如下:
=> A001 ok button click.
=> A002 cancel button click.
=> A003 ok button click.

運用這種模式我們可以很容易的統一固定在某一個層級(元素)上,監聽捕捉我們有興趣的事件並處理,好處是有一致的事件處理程序易於維護,且不需要刻意逐一的去維護內部元素的事件處理綁定,避免遺漏的發生,也減輕瀏覽器的負擔,增加執行效率,例如表格內各列的按鈕,當表格資料刷新時必須重新綁定一次,當在前端操作新增加入一筆資料時,又必須記得額外再做一次綁定,複雜程式碼及重工,且增加發生問題的機率。

 

3.自訂事件

前面範例是補捉原生的事件並判斷觸發事件的來源做不同的處理,如果要使用不同的自訂事件來區分應該怎麼做呢? 我們可以這麼做,首先將table各列的button增加onclick屬性如下,當觸發click事件時,觸發指定的okcancel自訂事件。
<input type="button" value="ok" onclick="$(this).trigger('ok');">
<input type="button" value="cancel" onclick="$(this).trigger('cancel');">
接著修改事件綁定只在handler div加上okcancel事件處理程式如下:
$(function(){
    $('#handler').on('ok',
    function (event) {
        addmsg('=> ' + $(event.target).closest('tr').data('id') + ' ok button click.');
    });
 
    $('#handler').on('cancel',
    function (event) {
        addmsg('=> ' + $(event.target).closest('tr').data('id') + ' cancel button click.');
    });
});
此時若分別點擊了表格第一列A001cancel button第二列A002ok button第三列A003cancel button則結果顯示文字如下,同樣達到目的。
=> A001 cancel button click.
=> A002 ok button click.
=> A003 cancel button click.
 

4.終止事件氣泡傳播

有時候因為網頁頁面上有較複雜的操作或不同層次中有相同的事件處理我們希望事件能夠在被觸發的元素上執行完成後即中止,不按照預設的行為向上傳播發散,減少瀏覽器處理執行時間或避免重複誤觸發其他事件執行,可以怎麼做呢? Javascript提供以下三個方式語法可運用:

(1) event.preventDefault()

當執行event.preventDefault()後會對元素事件終止預設行為(Stop Event Flow)例如如下範例,使用一個a hyper link元素,並綁定其click事件,在事件我們可以撰寫事件卻處理的程序邏輯及event.preventDefault()語法,當觸發click事件時hyper link就不會執行連結導頁的動作了,因為己經要求事件停止預設行為。
<a href="#">click me</a>
<script type="text/javascript">
$("a").click(function(event) {
    event.preventDefault();
    // do something…
});
</script>

(2) event.stopPropagation()

當執行event.stopPropagation()後會終止事件傳播例如如下範例,在a hyper link元素外層加上div容器,並個別綁定click事件,在a hyper link事件程序中加了event.stopPropagation()語法,則當hyper link被點取時只會執行a hyper link的事件,不會向上傳導觸發div click事件,但須特別注意的是a hyper link在事件處理後仍會執行預設連結導頁動作。
<div>
<a href="#">click me</a>
</div>
<script type="text/javascript">
$("div").click(function() {
    alert("div click!");
});
$("a").click(function(event) {
    event.stopPropagation();
    // do something…
});
</script>

(3) return false

當在事件處理程序中執行了return false其實包含了以下三種行為:

• event.preventDefault()

 event.stopPropagation()

 停止程序執行並回傳

如下範例,在a hyper link元素外層加上div容器,並個別綁定click事件,在a hyper link事件程序中加了return false,則當hyper link被點取時只會執行a hyper link事件顯示”link click 1!”提示訊息,不會顯示”link click 2!”提示訊息,並且取消連結導頁動作,亦不會向上傳導觸發div click事件。 
<div>
<a href="#">click me</a>
</div>
<script type="text/javascript">
$("div").click(function() {
    alert("div click!");
});
$("a").click(function() {
    alert("link click 1!");
    return false;
    alert("link click 2!");
});
</script>

5.Event Object

HTML DOM的規範裡事件都是以物件的型式存在,最後我們來看一下,當一個事件發生時可以從事件物件取得那些常用的資訊,詳細可參考w3schools

Event Object

屬性

說明

currentTarget

取得目前觸發事件的元素參考。

target

取得觸發事件的原始元素參考。

timeStamp

取得事件建立的日期時間。

type

取得事件的名稱,例如click

MouseEvent Object

屬性

說明

altKey

取得滑鼠事件觸發時是否有按下ALT鍵。

button

取得滑鼠事件觸發時哪一個滑鼠鍵被按下。

buttons

取得滑鼠事件觸發時哪些滑鼠鍵被按下。

clientX

取得滑鼠事件觸發時滑鼠指標對應目前視窗的水平座標位置點。

clientY

取得滑鼠事件觸發時滑鼠指標對應目前視窗的垂直座標位置點。

ctrlKey

取得滑鼠事件觸發時是否有按下CTRL鍵。

pageX

取得滑鼠事件觸發時滑鼠指標對應文本的水平座標位置點。

pageY

取得滑鼠事件觸發時滑鼠指標對應文本的垂直座標位置點。

relatedTarget

取得觸發滑鼠事件的元素

screenX

取得滑鼠事件觸發時滑鼠指標對應螢幕的水平座標位置點。

screenY

取得滑鼠事件觸發時滑鼠指標對應螢幕的垂直座標位置點。

shiftKey

取得滑鼠事件觸發時是否有按下SHIFT鍵。

KeyboardEvent Object

屬性

說明

altKey

取得鍵盤事件觸發時是否有按下ALT鍵。

ctrlKey

取得鍵盤事件觸發時是否有按下CTRL鍵。

charCode

取得鍵盤事件觸發時onkeypress事件按下的Unicode字元碼。

key

取得鍵盤事件觸發時按下的按鍵碼。

keyCode

取得鍵盤事件觸發時onkeypressonkeydownonkeyup事件的Unicode字元碼。

shiftKey

取得鍵盤事件觸發時是否有按下SHIFT鍵。

 

陳乾正