JavaScript 事件处理机制(事件模型)

DOM是树形结构,如果父子节点都绑定事件,事件处理先后的顺序如何决定?
这就涉及到事件流的概念:

一、事件流

事件流指的是从页面中接受事件的顺序。

事件冒泡

事件冒泡是一种从下往上的传播方式,事件由文档中嵌套层次最深的节点逐渐传播到高层父节点。

事件捕获

事件捕获流处理事件的顺序与事件冒泡相反。
JavaScript事件处理程序遵循先捕获(父节点先发现事件)后冒泡(从子节点向上传递)的规则。 如下图所示:
JavaScript 事件处理机制(事件模型)_第1张图片

二、DOM0事件模型

以鼠标点击事件为例,以下代码在两个div上分别定义了鼠标点击事件,这种事件的定义方式为DOM0事件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JavaScript事件流</title>
    <style>
        #out{
            width: 200px;
            height: 200px;
            background-color: blue;
        }
        #in{
            width: 100px;
            height: 100px;
            background-color: blueviolet;
        }
    </style>
</head>
<body>
    <div id="out">
        <div id="in">
        </div>
    </div>
    <script>
        var outer = document.getElementById("out");
        var inner = document.getElementById("in");
        outer.onclick = function () {
            console.log("点击了外部div");
        };
        inner.onclick = function () {
            console.log("点击了内部div");
        }
    </script>
</body>
</html>

当点击内部div时,控制台输出的顺序为:
JavaScript 事件处理机制(事件模型)_第2张图片
由此可见,对于DOM0级事件,浏览器是在冒泡阶段对触发的事件进行响应的。
此外对于DOM0级事件来说,同一元素的某一事件只能注册一个,后边注册的同种事件会覆盖之前注册的事件。正是因为这个特点,要想解除某一元素注册的DOM0级事件,只需要再将其事件注册为null即可。例如,要想解除以上代码中外部div的鼠标点击事件,只需要在后面加上一句以下代码即可:

outer.onclick = null;

三、DOM2事件模型

修改以上注册事件的方式即可定义DOM2级事件:

var outer = document.getElementById("out");
var inner = document.getElementById("in");
outer.addEventListener("click", function (e) {
    console.log("点击了外部div(DOM2级事件)");
}, false);
outer.addEventListener("click", function (e) {
    console.log("点击了外部div(DOM2级事件——第二个点击事件)");
}, false);
inner.addEventListener("click", function (e) {
    console.log("点击了内部div(DOM2级事件)");
}, false);

通过addEventListener方法注册的事件称为DOM2级事件,该方法一共接收3个参数:

  • 第一个参数:注册的事件类型(注意前面没有on)
  • 第二个参数:回调函数,即事件响应方法
  • 第三个参数:true/false,代表是否捕获,若设置为true,代表在事件捕获阶段触发事件;若设置为false,代表在事件冒泡阶段触发事件。一般情况下该参数均设置为false。

在修改了事件模型之后,点击内部div时控制台显示的消息为:
JavaScript 事件处理机制(事件模型)_第3张图片
因为设置了事件冒泡,所以浏览器按照事件冒泡的顺序处理事件:先触发内部div点击事件,再触发外部div点击事件。可以看到,不同于DOM0级事件,DOM2级事件允许为同一元素注册多个同类事件。
当将以上addEventListener函数的第三个参数均改为true(启用捕获)时,控制台输出的消息顺序变为:
JavaScript 事件处理机制(事件模型)_第4张图片
由于父节点先被捕获,所以首先触发了外部div的事件。
在DOM2级事件中,由于后注册的事件不会覆盖之前注册的事件,所以不能通过将元素事件注册为null的方式来解除事件。在DOM2级事件模型中,要想解除事件,需要调用removeEventListener函数,该函数的参数与addEventListener方法的参数完全一致,用于解除注册的DOM2级事件。这里需要注意的是,要想解除某一事件,必须把回调函数先保存起来,匿名函数无法移除

阻止冒泡

有的时候,当点击了内部div后,我们只希望触发内部div的点击方法,不希望触发外部div的点击方法,这时候就要用到阻止冒泡。
阻止冒泡的方法很简单,只要在内部div的点击方法中添加一行代码就可以实现阻止冒泡:

inner.addEventListener("click", function (e) {
    console.log("点击了内部div(DOM2级事件)");
    e.stopPropagation();  // 阻止冒泡
}, false);

阻止冒泡后,点击内部div时,控制台将不再输出外部div的点击消息:
JavaScript 事件处理机制(事件模型)_第5张图片

你可能感兴趣的:(前端)