回顾事件冒泡、事件捕获

241 阅读5分钟

【前言】

事件冒泡、事件捕获作为前端基础概念,相信前端“攻城狮”们都很熟悉。笔者在一次开发过程中遇到了问题,想通过阻止事件传播的方案去解决。但是事情并不顺利,并重新回顾一下事件冒泡、事件捕获。

【目录】

  1. 概念提出
  2. 概念解释
  3. 代码示例
  4. 阻止事件冒泡,事件捕获
  5. react中的合成事件系统
  6. 应用场景

【正文】

<div id="myDiv">
	<p id="myP">点击该段落</p>
</div><br>

上面的代码当中一个div元素当中有一个p子元素,如果两个元素都有一个click的处理函数,那么我们怎么才能知道哪一个函数会首先被触发呢?

概念提出

事件冒泡事件捕获的概念最早由微软和网景公司分别提出,是JavaScript中两种不同的事件传播方式。用来解决浏览器中不同层级元素之间的事件处理问题,也就是上述代码中哪个函数首先触发问题。

概念解释

事件冒泡:当一个元素被触发之后,会将事件一层一层由里向外一直传递到window对象。这个过程就像气泡从水底冒出来一样,所以叫做事件冒泡

事件捕获:与事件冒泡相反,由外向里。当一个元素被触发时,会先从window对象开始向下寻找目标元素,并在每个经过的元素上触发该事件。这个过程就像捕获鱼一样,所以叫做事件捕获

代码示例

假设我们有如下的HTML结构:

<div id="outer">
    <div id="inner">
        <button id="btn">Click Me</button>
    </div>
</div>

我们可以用JavaScript给不同的元素绑定点击事件,并打印出相应的信息:

var outer = document.getElementById("outer");
var inner = document.getElementById("inner");
var btn = document.getElementById("btn");

outer.addEventListener("click", function() {
    console.log("outer clicked");
});

inner.addEventListener("click", function() {
    console.log("inner clicked");
});

btn.addEventListener("click", function() {
    console.log("button clicked");
});

如果我们点击按钮,会看到控制台输出如下:

button clicked
inner clicked
outer clicked

这就是默认的事件冒泡模式,在这种模式下,最具体的元素(按钮)先接收到点击事件,然后逐级向上传播到较为不具体的元素(内部div、外部div)。

如果我们想使用事件捕获模式,我们需要给addEventListener方法传入第三个参数true:

var outer = document.getElementById("outer");
var inner = document.getElementById("inner");
var btn = document.getElementById("btn");

outer.addEventListener("click", function() {
    console.log("outer clicked");
}, true);

inner.addEventListener("click", function() {
    console.log("inner clicked");
}, true);

btn.addEventListener("click", function() {
    console.log("button clicked");
}, true);

这样,当我们点击按钮时,会看到控制台输出如下:

outer clicked
inner clicked
button clicked

这就是事件捕获模式,在这种模式下,最不具体的元素(外部div)先接收到点击事件,然后逐级向下传播到较为具体的元素(内部div、按钮)。

阻止事件冒泡,事件捕获

有时候我们可能不希望某个元素触发的事件继续传播到其他元素上,这时候我们可以使用stopPropagation方法来阻止事件的进一步传播。

例如,在上面的代码示例中,如果我们想让按钮点击时只输出button clicked而不输出inner clicked和outer clicked,我们可以这样修改:

var outer = document.getElementById("outer");
var inner = document.getElementById("inner");
var btn = document.getElementById("btn");

outer.addEventListener("click", function() {
    console.log("outer clicked");
});

inner.addEventListener("click", function() {
    console.log("inner clicked");
});

btn.addEventListener("click", function(e) {
    console.log("button clicked");
    e.stopPropagation(); // 阻止事件冒泡
});

这样当我们点击按钮时,就只会看到控制台输出button clicked。

同理,在捕获模式下,如果我们想让外部div点击时只输出outer clicked而不输出inner clicked和button clicked,我们可以这样修改:

var outer = document.getElementById("outer");
var inner = document.getElementById("inner");
var btn = document.getElementById("btn");

outer.addEventListener("click", function(e) {
    console.log("outer clicked");
    e.stopPropagation(); // 阻止事件捕获
}, true);

inner.addEventListener("click", function() {
    console.log("inner clicked");
}, true);

btn.addEventListener("click", function() {
    console.log("button clicked");
}, true);

这样当我们点击外部div时,就只会看到控制台输出outer clicked。

react中的合成事件系统

react中使用了自定义的合成事件系统(SyntheticEvent),它对原生DOM事件进行了封装和处理,以实现跨浏览器的一致性和高效的内存管理

react中的合成事件默认采用了冒泡模式,也就是说,如果你在一个嵌套的组件上绑定了同一个类型的事件处理函数,那么当你触发这个类型的事件时,会先执行最内层组件的函数,然后再执行外层组件的函数

如果你想要在react中使用捕获模式,你可以在绑定事件处理函数时,在事件名后面加上Capture后缀。例如:

<div onClick={handleClick}>
  <div onClickCapture={handleClickCapture}>
    Click me
  </div>
</div>

这样当你点击内层div时,会先执行handleClickCapture函数,然后再执行handleClick函数。

关于react中事件冒泡和事件捕获的并没有优劣之分,应用场景不同。

应用场景

一般来说,使用冒泡模式可以方便地利用委托机制,在父级元素上统一处理子元素的相同类型的事件。这样可以减少内存消耗和提高性能。

使用捕获模式可以在父级元素上提前拦截子元素触发的某些类型的事件,并进行相应的操作或阻止冒泡。这样可以实现一些特殊需求或增强安全性。

事件委托

<ul id="ul">
    <li>111111</li>
    <li>222222</li>
    <li>333333</li>
</ul> 
<button id="button">添加元素</button> 
<script> 
    let domUl = document.querySelector('#ul');
    let domButton = document.querySelector('#button');
    function handleUlClick(e) { //这里暂不考虑IE兼容
        if (e.target.nodeName.toLowerCase() === 'li') {
            console.log(e.target.innerText);
            e.target.style.background = 'red';
        }
    }
        domUl.addEventListener('click', handleUlClick);
        domButton.addEventListener('click', function() {
            let newLi = document.createElement('li');
            newLi.innerText = 'newnewnew';
            domUl.append(newLi); }); 
</script>

我们并没有将click事件绑定在每一个<li/>标签上,而是委托给了其父元素<ul/>

感谢您的阅读,有不足之处请为我指出!