JS事件机制浅析:事件捕获、事件冒泡和事件委托

2,584 阅读6分钟

一、引言

JavaScript是一种广泛使用的脚本语言,用于为网页添加交互性。JavaScript的事件机制是实现用户交互的重要组成部分,它允许我们对用户的交互做出响应。在处理事件时,我们需要了解事件的传播方式以及如何使用事件委托来提高性能。

二、什么是事件捕获和事件冒泡

事件机制是指当特定的操作(如点击按钮、移动鼠标等)在DOM元素上发生时,会触发相应的事件。JavaScript通过监听事件并绑定对应的处理函数来响应用户的操作,对用户的交互做出响应。

在JavaScript中,事件是以事件流的形式出现的,事件流顺序分为捕获和冒泡两种方式。 事件流分为三个阶段:1.捕获阶段 2.目标阶段 3.冒泡阶段。

事件捕获和事件冒泡是处理DOM事件的两种不同的机制。

三、事件捕获和冒泡的顺序

事件捕获的顺序是从最外层的元素开始,逐级向内部元素传播,直到达到目标元素。 例如:window -> document -> html -> body -> div。

事件冒泡的顺序是从目标元素开始,逐级向外层元素传播,直到达到最外层的元素。 例如:div -> body -> html -> document -> window。

1.jpg (图片来自网络)

四、addEventListener

在JavaScript中,我们可以使用addEventListener方法来绑定捕获和冒泡事件。

element.addEventListener(event, handler, useCapture);

其中,event表示要绑定的事件类型,function表示事件触发时要执行的函数,useCapture是一个可选的参数,用于指定事件是使用捕获还是冒泡阶段进行处理。

当useCapture为false或未提供时,事件将在冒泡阶段进行处理;当useCapture为true时,事件将在捕获阶段进行处理。

<div id="blueBox">
   <div id="yellowBox">
     <div id="greenBox"></div>
   </div>
</div>

<script> 
 let blueBox = document.getElementById('blueBox');
 let yellowBox = document.getElementById('yellowBox');
 let greenBox = document.getElementById('greenBox');
 blueBox.addEventListener('click', () => {
     console.log('blueBox')
 })
 yellowBox.addEventListener('click', () => {
     console.log('yellowBox')
 })
 greenBox.addEventListener('click', () => {
     console.log('greenBox');
 })
</script>

QQ截图20230623223044.jpg

当点击绿色方块时,输出greenBox、yellowBox、blueBox,因为绿色包含在黄色里,黄色和绿色被包含蓝色中。addEventListener不写第三个参数时,事件将在冒泡阶段进行处理,从目标元素开始,逐级向外层元素传播,直到达到最外层的元素,也就是绿色、黄色、蓝色。

同理,点击黄色时,输出yellowBox、blueBox。点击蓝色时,输出blueBox。

 blueBox.addEventListener('click', () => {
     console.log('blueBox')
 }, true);
 yellowBox.addEventListener('click', () => {
     console.log('yellowBox')
 })
 greenBox.addEventListener('click', () => {
     console.log('greenBox');
 }, true);

如上代码所示,将blueBox和greenBox的第三个参数设成true,点击绿色(greenBox),将输出blueBox、greenBox、yellowBox,因为blueBox和greenBox的事件将在捕获阶段进行处理,yellowBox的事件将在冒泡阶段进行处理。捕获的顺序是从最外层的元素开始,逐级向内部元素传播,直到达到目标元素,也就是蓝色、绿色,之后才是冒泡事件yellowBox。

如果将第三个参数全部设成true,点击绿色(greenBox),将输出blueBox、yellowBox、greenBox,因为事件将在捕获阶段进行处理,事件捕获的顺序是从最外层的元素开始,逐级向内部元素传播,直到达到目标元素,也就是蓝色、黄色、绿色。

五、阻止事件传播

1.event.stopPropagation()

调用该方法会阻止事件继续传播,但不会阻止其他事件处理程序被触发。也就是说,如果一个元素上绑定了多个事件处理程序,调用该方法只会阻止事件传播到更高层级的元素,而不会阻止同一元素上的其他事件处理程序被触发。

 blueBox.addEventListener('click', () => {
     console.log('blueBox')
 }, true);
 yellowBox.addEventListener('click', () => {
     console.log('yellowBox')
 })
 greenBox.addEventListener('click', (event) => {
     console.log('greenBox');
     event.stopPropagation();
 }, true);

在上面的示例中,当点击绿色方块时,调用event.stopPropagation()会阻止事件继续传播到外层元素,所以会阻止冒泡到黄色,而蓝色是在捕获阶段执行的,不会阻止,所以输出"blueBox和greenBox"

blueBox.addEventListener('click',() => {
  console.log('blueBox')
});
yellowBox.addEventListener('click',(event) => {
  console.log('yellowBox');
});
yellowBox.addEventListener('click',(event) => {
  console.log('yellowBox222');
});
greenBox.addEventListener('click',(event) => {
  console.log('greenBox');
  event.stopPropagation();
});
greenBox.addEventListener('click',(event) => {
  console.log('greenBox222');
  console.log('greenBox333');
});

该方法不会阻止同一元素上的其他事件处理程序被触发,在上述代码中,输出结果是greenBox、greenBox222、greenBox333。

2.event.stopImmediatePropagation()

调用该方法会阻止事件继续传播,并且会阻止同一元素上的其他事件处理程序被触发。也就是说,如果一个元素上绑定了多个事件处理程序,调用该方法会立即停止事件传播,并且不会触发同一元素上的其他事件处理程序。

blueBox.addEventListener('click',() => {
  console.log('blueBox')
});
yellowBox.addEventListener('click',(event) => {
  console.log('yellowBox');
});
yellowBox.addEventListener('click',(event) => {
  console.log('yellowBox222');
});
greenBox.addEventListener('click',(event) => {
  console.log('greenBox');
  event.stopImmediatePropagation();
});
greenBox.addEventListener('click',(event) => {
  console.log('greenBox222');
  console.log('greenBox333');
});

在上面的示例中,当点击绿色方块(greenBox)时,调用event.stopImmediatePropagation()会阻止事件继续传播到外层元素,并且还会阻止自身的其他事件的触发,只会输出"greenBox",而不会输出其他内容。

六、事件委托

事件委托也称为事件代理(Event Delegation),事件委托是一种将事件处理程序绑定到一个父元素上,而不是将事件处理程序绑定到每个子元素上的技术。通过事件委托,可以减少事件处理程序的数量,提高性能和代码的可维护性。

示例代码

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

<script>
     let li = document.getElementsByTagName("li");
     for (let i = 0; i < li.length; i++) {
       li[i].addEventListener("click", () => {
         console.log(li[i].innerHTML)
       })
     }
</script>

QQ截图20230624075441.jpg

如上述代码所示,点击某一数字,就会输出对应内容。节点少的时候还好,如果节点多达上千上万个,就需要声明相当多的事件函数,比较消耗内存。而且如果列表经常发生动态变更,也会导致大量监听事件的移除和绑定。

在这种情况下,事件委托就可以体现它的优势了。

事件委托正是利用事件流的冒泡特性,将本来要绑定到多个元素的事件函数,委托到了其祖先元素上

//事件代理  节约内存 提升性能(不需要注销子节点)
let ul = document.getElementById("ul");
ul.addEventListener("click", (event) => {
    console.log(event.target.innerHTML);
})

我们通过将事件处理程序绑定到父元素ul上,当点击列表项时,通过 event 对象拿到必要的信息,会打印出被点击的列表项的内容。如此这般,不管li有多少,更新多频繁,我们只需要维护一个函数就够了

七、最后的话

JavaScript事件机制是Web开发中不可或缺的一部分,了解事件机制、阻止事件传播和事件委托技术可以帮助我们更好地处理用户的交互,提高代码的性能和可维护性。

能力一般,水平有限,本文可能存在纰漏或错误,如有问题欢迎大佬指正,感谢你阅读这篇文章,如果你觉得写得还行的话,不要忘记点赞、评论、收藏哦!祝生活愉快!