JavaScript——事件冒泡——图解

目录

1 引言

2 理论背景

3 Example01——简单事件传播演示

3.1 遍历所有节点

3.2 日志记录事件

3.3 完整示例01代码

3.4 应用程序截图

3.5 执行——查找所有事件目标

3.6 执行——激活事件处理程序

3.7 执行——单击事件

3.8 评论

4 Example02

4.1 代码

4.2 截图

4.3 执行——我们的点击

5 结论

6 参考资料


1 引言

本文的目的是提供浏览器DOM事件的冒泡和捕获的说明性示例。这并不是一个完整的教程,而是事件如何在DOM中传播的概念证明

2 理论背景

每个DOM节点都可以生成一个事件。事件是某事发生的信号。

为了对事件做出反应,我们需要为节点分配一个事件处理程序。有三种方法可以分配处理程序:

  1. Html属性用法。例如:οnclick=”myhandler(e)”;
  2. DOM属性用法。例如:element.οnclick=myhandler;
  3. JavaScript方法。例如:element.addEventListener(e, myhandler, phase);

addEventListener方法的存在源于这样一个事实,即DOM中的每个Node都继承自EventTarget[2],它充当根抽象类。因此,每个Node都可以通过事件处理程序接收事件并对它们做出反应。

DOM中的大多数事件都会传播。通常,事件传播分为三个阶段:

  1. 捕获阶段:从窗口对象传播到特定目标事件。
  2. 目标阶段:事件已达到其目标。
  3. 冒泡阶段:从目标向窗口对象传播。

事件发生时将抛出事件对象。它包含描述该事件的属性。我们非常感兴趣的两个属性:

  • event.currentTarget——处理事件的对象(例如,实际单击的元素的某个父元素,通过冒泡行为获取该事件)
  • event.target——启动事件的目标元素(例如,实际单击该事件的元素)

3 Example01——简单事件传播演示

3.1 遍历所有节点

首先,我们要创建一个所有感兴趣的事件收件人的列表。这将包括我们的 DOM plus documentwindow对象中的所有节点。代码如下:

function getDescendants(node, myArray = null) {
    //note that we are looking for all Nodes, not just Elements
    //going recursively into dept
    var i;
    myArray = myArray || [];
    for (i = 0; i < node.childNodes.length; i++) {
        myArray.push(node.childNodes[i])
        getDescendants(node.childNodes[i], myArray);
    }
    return myArray;
}

function CreateListOfEventRecipients() {
    let result;
    //get all Nodes inside document
    result = getDescendants(document);
    //add Document object
    result.push(window.document);
    //add Window object
    result.push(window);
    return result;
}

3.2 日志记录事件

接下来,我们要在事件处理程序函数中很好地记录事件。代码如下:

function EventDescription(e, handlerPhase) {
    //function to log info from event object
    //here are properties, that are important
    //->handlerPhase -> we log info from which handler this event is coming 
    //                  from, is this from bubbling of capturing handler
    //->e.toString() -> class of event object
    //->e.type -> type of even, for example "click"
    //->e.timeStamp -> timestamp of event in milliseconds,
    //                 counted from load of the page
    //->e.target -> real target (object) of event, for example
    //              real object that was clicked on
    //->e.currentTarget -> current target (object) that is throwing 
    //                     this event, since event got here by either
    //                     bubbling of capturing propagation
    //->e.eventPhase -> real phase of event, regardless of handler that
    //                  created event handler, can be 1-capturing, 
    //                  2-target, 3-bubbling phase
    /* Sample execution:
    6600.900000095367[object PointerEvent]-----------
    ......EventType:click..HandlerPhase:Capturing..EventPhase:1 (Capturing)
    ......Target:[object HTMLElement], nodeName:B, id:Bold4
    ......CurrentTarget:[object HTMLDivElement], nodeName:DIV, id:Div1
    */
    const dots = "......";
    let result;
    if (e !== undefined && e !== null) {
        let eventObject = e.toString();
        let eventType = (e.type) ?
            (e.type.toString()) : undefined;
        let eventTimestamp = (e.timeStamp) ?
            (e.timeStamp.toString()) : undefined;
        let eventTarget = (e.target) ? ObjectDescription(e.target) : undefined;
        let eventCurrentTarget = (e.currentTarget) ?
            ObjectDescription(e.currentTarget) : undefined;
        let eventPhase = (e.eventPhase) ?
            PhaseDescription(e.eventPhase) : undefined;

        result = "";
        result += (eventTimestamp) ? eventTimestamp : "";
        result += (eventObject) ? eventObject : "";
        result += "-----------<br>";
        result += dots;
        result += (eventType) ? ("EventType:" + eventType) : "";
        result += (handlerPhase) ? ("..HandlerPhase:" + handlerPhase) : "";
        result += (eventPhase) ? ("..EventPhase:" + eventPhase) : "";
        result += "<br>";
        result += (eventTarget) ? (dots + "Target:" + eventTarget + "<br>") : "";
        result += (eventCurrentTarget) ? (dots + "CurrentTarget:" +
            eventCurrentTarget + "<br>") : "";
    }
    return result;
}  

3.3 完整示例01代码

这是完整的Example01代码,因为大多数人都喜欢可以复制粘贴的代码。

<!DOCTYPE html>
<html>

<body>
    <!--Html part---------------------------------------------->
    <div id="Div1" style="border: 1px solid; padding:10px; 
        margin:20px; background-color:aqua;">
        Div1
        <div id="Div2" style="border: 1px solid; padding:10px; 
            margin:20px;background-color:chartreuse">
            Div2
            <div id="Div3" style="border: 1px solid; padding:10px;
                margin:20px; background-color:yellow; ">
                Div3<br/>
                Please click on 
                <b id="Bold4" style="font-size:x-large;">bold text</b> only.
            </div>
        </div>
    </div>
    <hr />
    <h3>Example 01</h3>
    <br />
    <button onclick="task1()"> Task1-List of all nodes</button><br />
    <br />
    <button onclick="task2()"> Task2-Activate EventHandlers</button><br />
    <br />
    <button onclick="task3()"> Task3-Clear Output Box (delayed 1 sec)</button><br />
    <hr />

    <h3>Output</h3>
    <div id="OutputBox" style="border: 1px solid; min-height:20px">
    </div>

    <!--JavaScript part---------------------------------------------->
    <script>

        function OutputBoxClear() {
            document.getElementById("OutputBox").innerHTML = "";
        }

        function OutputBoxWriteLine(textLine) {
            document.getElementById("OutputBox").innerHTML
                += textLine + "<br/>";
        }

        function OutputBoxWrite(textLine) {
            document.getElementById("OutputBox").innerHTML
                += textLine;
        }

        function ObjectDescription(oo) {
            //expecting Node or Window or Document
            //logging some basic info to be able to 
            //identify which object is that in the DOM tree
            let result;

            if (oo != null) {
                result = "";
                result += oo.toString();

                if (oo.nodeName !== undefined) {
                    result += ", nodeName:" + oo.nodeName;
                }

                if (oo.id !== undefined && oo.id !== null
                    && oo.id.trim().length !== 0) {
                    result += ", id:" + oo.id;
                }

                if (oo.data !== undefined) {
                    let myData = oo.data;
                    let length = myData.length;
                    if (length > 30) {
                        myData = myData.substring(0, 30);
                    }
                    result += `, data(length ${length}):` + myData;
                }
            }

            return result;
        }

        function PhaseDescription(phase) {
            //some text to explain phase numbers
            let result;
            if (phase !== undefined && phase !== null) {
                switch (phase) {
                    case 1:
                        result = "1 (Capturing)";
                        break;
                    case 2:
                        result = "2 (Target)";
                        break;
                    case 3:
                        result = "3 (Bubbling)";
                        break;
                    default:
                        result = phase;
                        break;
                }
            }
            return result;
        }

        function EventDescription(e, handlerPhase) {
            //function to log info from event object
            //here are properties, that are important
            //->handlerPhase -> we log info from which handler this event is coming 
            //                  from, is this from bubbling of capturing handler
            //->e.toString() -> class of event object
            //->e.type -> type of even, for example "click"
            //->e.timeStamp -> timestamp of event in milliseconds,
            //                 counted from load of the page
            //->e.target -> real target (object) of event, for example
            //              real object that was clicked on
            //->e.currentTarget -> current target (object) that is throwing 
            //                     this event, since event got here by either
            //                     bubbling of capturing propagation
            //->e.eventPhase -> real phase of event, regardless of handler that
            //                  created event handler, can be 1-capturing, 
            //                  2-target, 3-bubbling phase
            /* Sample execution:
            6600.900000095367[object PointerEvent]-----------
            ......EventType:click..HandlerPhase:Capturing..EventPhase:1 (Capturing)
            ......Target:[object HTMLElement], nodeName:B, id:Bold4
            ......CurrentTarget:[object HTMLDivElement], nodeName:DIV, id:Div1
            */
            const dots = "......";
            let result;
            if (e !== undefined && e !== null) {
                let eventObject = e.toString();
                let eventType = (e.type) ?
                    (e.type.toString()) : undefined;
                let eventTimestamp = (e.timeStamp) ?
                    (e.timeStamp.toString()) : undefined;
                let eventTarget = (e.target) ? ObjectDescription(e.target) : undefined;
                let eventCurrentTarget = (e.currentTarget) ?
                    ObjectDescription(e.currentTarget) : undefined;
                let eventPhase = (e.eventPhase) ?
                    PhaseDescription(e.eventPhase) : undefined;

                result = "";
                result += (eventTimestamp) ? eventTimestamp : "";
                result += (eventObject) ? eventObject : "";
                result += "-----------<br>";
                result += dots;
                result += (eventType) ? ("EventType:" + eventType) : "";
                result += (handlerPhase) ? ("..HandlerPhase:" + handlerPhase) : "";
                result += (eventPhase) ? ("..EventPhase:" + eventPhase) : "";
                result += "<br>";
                result += (eventTarget) ? (dots + "Target:" + eventTarget + "<br>") : "";
                result += (eventCurrentTarget) ? (dots + "CurrentTarget:" +
                    eventCurrentTarget + "<br>") : "";
            }
            return result;
        }

        function getDescendants(node, myArray = null) {
            //note that we are looking for all Nodes, not just Elements
            //going recursively into dept
            var i;
            myArray = myArray || [];
            for (i = 0; i < node.childNodes.length; i++) {
                myArray.push(node.childNodes[i])
                getDescendants(node.childNodes[i], myArray);
            }
            return myArray;
        }

        function CreateListOfEventRecipients() {
            let result;
            //get all Nodes inside document
            result = getDescendants(document);
            //add Document object
            result.push(window.document);
            //add Window object
            result.push(window);
            return result;
        }

        function MyEventHandler(event, handlerPhase) {
            OutputBoxWrite(EventDescription(event, handlerPhase));
        }

        function BubblingEventHandler(event) {
            MyEventHandler(event, "Bubbling");
        }

        function CapturingEventHandler(event) {
            MyEventHandler(event, "Capturing");
        }

        window.onerror = function (message, url, line, col, error) {
            OutputBoxWriteLine(`Error:${message}\n At ${line}:${col} of ${url}`);
        };

        function task1() {
            //we need to delay for 1 second aaa 
            //to avoid capturing click on button itself 
            setTimeout(task1_worker, 1000);
        }
        function task1_worker() {
            //show all Nodes plus Window plus Document 
            //they all receive events
            OutputBoxClear();
            OutputBoxWriteLine("Task1");
            let arrayOfEventRecipientCandidates = CreateListOfEventRecipients();

            for (let i = 0; i < arrayOfEventRecipientCandidates.length; ++i) {
                let description = ObjectDescription(arrayOfEventRecipientCandidates[i]);
                OutputBoxWriteLine(`[${i}] ${description}`);
            }
        }

        function task2() {
            OutputBoxClear();
            OutputBoxWriteLine("Task2");
            //we need to delay for 1 second creation of EventsHandlers
            //to avoid capturing click on button Task2 itself 
            setTimeout(task2_worker, 1000);
        }

        function task2_worker() {
            //create list of all Event Recipients
            //and assigning Events Handlers
            let arrayOfEventRecipientCandidates = CreateListOfEventRecipients();

            for (let i = 0; i < arrayOfEventRecipientCandidates.length; ++i) {
                //we check that each member of list can receive events
                if ("addEventListener" in arrayOfEventRecipientCandidates[i]) {
                    //adding Event Handlers for Bubbling phase
                    //actually, this will be handler for bubbling and target phase
                    arrayOfEventRecipientCandidates[i].addEventListener
                                ("click", BubblingEventHandler);
                    //adding Event Handlers for Capturing phase
                    //actually, this will be handler for capturing and target phase
                    arrayOfEventRecipientCandidates[i].addEventListener
                                ("click", CapturingEventHandler, true);
                }
                else {
                    //here printout if any object from the list can not receive Events
                    let description = "Object does not have addEventListener:" +
                        ObjectDescription(arrayOfEventRecipientCandidates[i]);
                    OutputBoxWriteLine(`[${i}] ${description}`);
                }
            }
        }

        function task3() {
            //we need to delay for 1 second aaa 
            //to avoid capturing click on button Task3 itself 
            setTimeout(OutputBoxClear, 1000);
        }

    </script>
</body>
<!--
Output

    -->

</html>

3.4 应用程序截图

下面是应用程序的样子:

3.5 执行——查找所有事件目标

首先,我们将显示我们的方法找到的所有事件目标。它包括所有节点以及documentwindow对象。该列表中大约有60个对象。请注意,它包括所有节点,包括文本和注释节点。

3.6 执行——激活事件处理程序

然后,我们为列表中的所有对象激活事件处理程序。

3.7 执行——单击事件

现在我们点击一下。下面是应用程序中的事件日志:

3.8 评论

对于那些喜欢DOM树形图的人来说,这里是应用程序中发生的事情的图表。

  • 请注意,上图不包括documentwindow对象,从日志中可以看出,这些对象也是click事件的接收者。
  • 请注意,虽然我们实际上单击了文本Node #text-6,但该Node没有收到该事件,但包含它的Element B Id:Bold4 确实收到了click事件。

4 Example02

我创建此示例的原因是,我在Internet上看到这样一种说法:在Target阶段,事件并不总是按Capturing-Bubbling顺序运行,而是按在代码中定义的顺序运行。我发现这些说法是不真实的,至少对于我用于测试的这个版本的Chrome是这样。

4.1 代码

我不会把整个代码放在这里,因为它与前面的例子类似。以下是关键部分:

unction task1() {
    OutputBoxClear();
    OutputBoxWriteLine("Task1-Activate EventHandlers");

    //Div1 event handlers
    let div1=document.getElementById("Div1");
    div1.addEventListener("click", (e)=>MyEventHandler(e, "Bubbling"));
    div1.addEventListener("click", (e)=>MyEventHandler(e, "Capturing"), true);

    //Div2 event handlers
    let div2=document.getElementById("Div2");
    div2.addEventListener("click", (e)=>MyEventHandler(e, "Bubbling"));
    div2.addEventListener("click", (e)=>MyEventHandler(e, "Capturing"), true);

    //Div3 event handlers
    //we deliberately mix defining order of bubbling and capturing events
    //to see order when events are fired (**)
    let div3=document.getElementById("Div3");
    //actually, this will be handler for bubbling and target phase
    div3.addEventListener("click", (e)=>MyEventHandler(e, "Bubbling"));
    //actually, this will be handler for capturing and target phase
    div3.addEventListener("click", (e)=>MyEventHandler(e, "Capturing"), true);
    //actually, this will be handler for bubbling and target phase
    div3.addEventListener("click", (e)=>MyEventHandler(e, "Bubbling2"));
    //actually, this will be handler for capturing and target phase
    div3.addEventListener("click", (e)=>MyEventHandler(e, "Capturing2"), true);
}       

请注意,在(**)中,我们为同一元素交错定义了CapturingBubbling事件处理程序。

4.2 截图

这是应用程序屏幕截图。

4.3 执行——我们的点击

现在我们点击一下。下面是生成的日志:

据我所知(在此版本的Chrome上),在Target阶段,所有事件都首先按照Event-Handlers Capturing的顺序运行,然后是Bubbling。处理程序不按(**)中定义的顺序运行。

如果您查看目标阶段(黄色),您将看到此执行中事件的顺序“Capturing”“Capturing 2”“Bubbling”“Bubbling 2”。互联网上一些人声称,顺序将与定义(**)中的顺序相同,例如“Capturing”“Bubbling”“Bubbling 2”“Bubbling2”。但是,我的这个实验反驳了这种说法,至少对于这个版本的Chrome来说是这样。

5 结论

在本文中,我们提供了一个简单的概念验证应用程序,展示了DOM中的事件传播是如何工作的。

6 参考资料

https://www.codeproject.com/Articles/5372580/JavaScript-Events-Bubbling-Illustrated

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值