JS原理 - 理解事件捕获、冒泡与委托

事件捕获和事件冒泡都是为解决 Js 事件处理顺序的问题而提出来的概念,是 DOM 事件流中的两个阶段,浏览器会遵循先捕获后冒泡的规则依次进入事件捕获阶段、到达目标阶段、事件冒泡阶段,可以在这三个阶段中对 Js 事件作出响应。

区别

事件捕获:事件会由最外层(document 对象)接收,然后逐级下传至最具体的元素,常用于截获事件。

事件冒泡:事件会从最具体的元素接收,逐级上传至最外层,常用于对事件作出响应。

举个例子:

<div id="outer">
    <p id="inner">Click me!</p>
</div>

如果给 inner 和 outer 都设置 click 监听,点击 inner 元素,事件捕获阶段, click 事件的接收顺序是: document > html > body > div > p,事件冒泡阶段,click 事件的接收顺序是:p > div > body > html > document

实例

[查看初始例子]

js部分:

const a = document.getElementById("a"),
      b = document.getElementById("b"),
      c = document.getElementById("c");

a.addEventListener(
    "click",
    function () {
        console.log("a1");
    },
    true
);

a.addEventListener("click", function () {
    console.log("a2");
});

b.addEventListener(
    "click",
    function () {
        console.log("b1");
    },
    true
);

b.addEventListener("click", function () {
    console.log("b2");
});

c.addEventListener("click", function () {
    console.log("c1");
});

c.addEventListener(
    "click",
    function () {
        console.log("c2");
    },
    true
);

当点击元素 a 时输出:a1、a2

当点击元素 c 时输出:a1、b1、c2、c1、b2、a2

stopPropagation

stopPropagation 用于阻止捕获和冒泡阶段中当前事件的进一步传播

他还有一个孪生兄弟 stopImmediatePropagation,他们的区别在于后者会阻止监听同一事件的其他事件监听器被调用,举个例子:

如果使用stopPropagation,那么点击 a 元素的时候会打印出 a1、ax、a2

a.addEventListener("click", function () {
    console.log("a1");
}, true);

a.addEventListener("click", function (ev) {
    ev.stopPropagation();
    console.log("ax");
}, true);

a.addEventListener("click", function () {
    console.log("a2")
}, true);

如果使用的是stopImmediatePropagation,那么打印出来的结果是 a1、ax,没有 a2 了,因为被阻止监听了。

事件委托

原理

事件委托就是在父级元素上设置监听,当父元素监听到事件时,在冒泡阶段判断目标元素(target)与委托的元素是否一致,如果一致则触发方法,举个例子:

<ul id="list">
  <li>item 1</li>
  <li class="item">item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
<script>
    document.querySelector('#list').addEventListener('click',ev => {
        if( ev.target.matches('li.item') ){
            console.log('li click')
        };
    })
</script>

上述代码在 ul 上委托了点击事件,当点击 ul 里面的任意元素时,便会监听到 click 事件,然后使用.matches()方法判断目标元素和委托元素是否一致,如果一致则执行方法。

优缺点

使用事件委托的好处在于:

  1. 避免了多次类似的事件绑定与解绑,减少内存消耗。
  2. 支持动态绑定,与 AJAX 配合更好。

不过事件委托也有它的局限性:

  1. 事件委托基于事件冒泡,focusblur等事件不支持委托。
  2. 如果有一层元素设置了 event.stopPropagation()阻止了事件冒泡,那么委托就会失败。

封装

为了更好的复用事件委托,可以将它封装成公共方法:

function eventDelegate(parentSelector, targetSelector, event, foo){
    document.querySelector(parentSelector).addEventListener(event, ev => {
        if( ev.target.matches(targetSelector) ){
            foo.call(ev.target);
        }
    });
};
eventDelegate('#list','li.item','click',function(){
    console.log(this) // => <li class="item">item 2</li>
});

JS原理 - 理解事件捕获、冒泡与委托

https://hashencode.github.io/post/d73eb7a7/

作者

BiteByte

发布于

2020-08-08

更新于

2024-11-15

许可协议