详解浏览器事件捕获,冒泡

详解浏览器事件捕获,冒泡

浏览器事件模型中的过程主要分为三个阶段:捕获阶段、目标阶段、冒泡阶段。
这里先看一下这张经典的图

事件模型

第三个参数

这里要注意addEventListener的第三个参数, 如果为true,就是代表在捕获阶段执行。如果为false,就是在冒泡阶段进行
空口这么说可能不好理解,咱们来看一下代码。

<!DOCTYPE html>

<html>
    <head>
        <meta charset="UTF-8">
    </head>
  <body>
    <div id="parent" class="flex-center">
        parent
        <p id="child" class="flex-center">
            child
            <span id='son' class="flex-center">
                son
                <a href="https://www.baidu.com" id="a-baidu">点我啊</a>
            </span>
        </p>
    </div>
  </body>
  <script type="text/javascript" src="index.js"></script>
  <style>
    #parent {
        background-color: bisque;
        width: 700px;
        height: 700px;

    }

    #child {
        background-color: chocolate;
        width: 500px;
        height: 500px;
    }

    #son {
        background-color: crimson;
        width: 300px;
        height: 300px;
    }

    .flex-center {
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 20px;
    }
  </style>
</html>

复制代码
const parent = document.getElementById("parent");
const child = document.getElementById("child");
const son = document.getElementById("son");
// const baidu = document.getElementById("a-baidu");

// baidu.addEventListener('click', function (e) {
//     e.preventDefault();
// })

window.addEventListener("click", function (e) {
    // e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
    console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

parent.addEventListener("click", function (e) {
    // e.stopPropagation(); 不再派发事件。终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播。调用该方法后,该节点上处理该事件的处理程序将被调用,事件不再被分派到其他节点。

    // e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
    console.log("parent 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

child.addEventListener("click", function (e) {
    console.log("child 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

son.addEventListener("click", function (e) {
    console.log("son 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

window.addEventListener("click", function (e) {
    // e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
    console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

parent.addEventListener("click", function (e) {
    console.log("parent 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

child.addEventListener("click", function (e) {
    console.log("child 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

son.addEventListener("click", function (e) {
    console.log("son 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
复制代码

点击红色方块

image-20210603140310257

阻止事件传播

  • e.stopPropagation()

大家经常听到的可能是阻止冒泡,实际上这个方法不只能阻止冒泡,还能阻止捕获阶段的传播。

  • stopImmediatePropagation()

如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行。

看一下代码

阻止默认行为

e.preventDefault()

e.preventDefault()可以阻止事件的默认行为发生,默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为

看一下代码

兼容性

attachEvent——兼容:IE7、IE8; 不支持第三个参数来控制在哪个阶段发生,默认是绑定在冒泡阶段
addEventListener——兼容:firefox、chrome、IE、safari、opera;

绑定事件的运用,以及封装一个多浏览器兼容的绑定事件函数

大家常见的一个面试题可能是ul + li,点击每个li alert对应的索引,这里就给大家来写一下看看

  • 先来给每个li绑定事件
  • 再来写一个事件委托的方式
<!DOCTYPE html>

<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
    <ul id="ul">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
    </ul>
</body>
<script type="text/javascript">
const ul = document.querySelector("ul");
ul.addEventListener('click', function (e) {
    const target = e.target;
  if (target.tagName.toLowerCase() === "li") {
    const liList = this.querySelectorAll("li");
    index = Array.prototype.indexOf.call(liList, target);
    alert(`内容为${target.innerHTML}, 索引为${index}`);
  }
})

// const liList = document.getElementsByTagName("li");

// for(let i = 0; i<liList.length; i++){
//     liList[i].addEventListener('click', function(e){
//         alert(`内容为${e.target.innerHTML}, 索引为${i}`);
//    })
// }
</script>
  <style>
    #ul {
        background-color: gray;
        width: 700px;
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: center;
    }

    li {
        margin-bottom: 20px;
        width: 80%;
        height: 100px;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: lightgoldenrodyellow;
        font-size: 20px;
        font-weight: bold;
    }
  </style>
</html>

复制代码

封装一个多浏览器兼容的绑定事件函数

class BomEvent {
    constructor(element) {
        this.element = element;
    }

    addEvent(type, handler) {
        if (this.element.addEventListener) {
            //事件类型、需要执行的函数、是否捕捉
            this.element.addEventListener(type, handler, false);
        } else if (this.element.attachEvent) {
            this.element.attachEvent('on' + type, function () {
                handler.call(element);
            });
        } else {
            this.element['on' + type] = handler;
        }
    }

    removeEvent(type, handler) {
        if (this.element.removeEnentListener) {
            this.element.removeEnentListener(type, handler, false);
        } else if (element.datachEvent) {
            this.element.detachEvent('on' + type, handler);
        } else {
            this.element['on' + type] = null;
        }
    }
}

// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
function stopPropagation(ev) {
    if (ev.stopPropagation) {
        ev.stopPropagation(); // 标准w3c
    } else {
        ev.cancelBubble = true; // IE
    }
}
// 取消事件的默认行为
function preventDefault(event) {
    if (event.preventDefault) {
        event.preventDefault(); // 标准w3c
    } else {
        event.returnValue = false; // IE
    }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享