JavaScript 中的事件冒泡:使用冒泡和捕获高效处理 JavaScript 事件
JavaScript 使我们的 Web 应用程序具备交互性。它可以识别用户发起的事件,例如鼠标点击、鼠标滚轮滚动、键盘按键等等。流畅地处理这些用户操作对于提供良好的用户体验至关重要。今天,我们将以鼠标点击事件为例,学习如何高效地处理 JavaScript 事件。
addEventListener方法
JavaScript 有一个内置方法,addEventListener可以将其添加到 HTML 节点上。它总共接受 3 个参数,如下所示:
- 事件名称。
- 当指定事件触发时,用于执行某些代码的回调函数。
- 可选参数:捕获的布尔值。(默认设置为 false)。
<div id="div1">I am a div1</div>
const div1 = document.getElementById("div1");
div1.addEventListener("click", function() {
console.log("div1 clicked");
});
正如你所料,点击“我是一个div”文本会在控制台输出“div1被点击”。让我们在HTML中用一个新的div元素包裹这段文本。你能猜到现在点击这段文本会输出什么吗?
<div id="div1">
<div id="div2">I am a div1</div>
</div>
const div1 = document.getElementById("div1");
div1.addEventListener("click", function() {
console.log("div1 clicked");
});
即使我们点击了 id 为“div2”的 div,结果仍然保持不变,并打印出“我是 div1”。
事件酝酿中
在 JavaScript 中,事件默认会冒泡。事件冒泡是指事件从最内层的 HTML 元素开始,沿着 DOM 层级向上传递,直到到达监听该事件的元素。这种传递过程也常被称为事件传播或事件委托。
在上面的例子中,点击文本“I am a div1”等同于点击#div2。因为我们在父元素#div1上设置了事件监听器,所以事件会触发最内层的子元素#div2并向上冒泡。
这里还有一个例子。我们再用 JavaScript 给 div2 添加一个事件监听器。
<div id="div1">
<div id="div2">I am a div</div>
</div>
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
div1.addEventListener("click", function() {
console.log("div1 clicked");
});
div2.addEventListener("click", function() {
console.log("div2 clicked");
});
以下是事件冒泡的结果。
div2 clicked
div1 clicked
注意,我们还可以向根级元素(例如 html 和 body)添加事件监听器,事件会一直冒泡到这些元素。以下是事件层级结构:
目标 -> 主体 -> HTML -> 文档 -> 窗口
停止传播
有时,你不希望事件沿某个方向传播,这时可以使用stopPropagation()事件对象。事件对象作为参数传递给回调函数。
...
div2.addEventListener("click", function(event) {
event.stopPropagation();
console.log("div2 clicked");
});
结果:
div2 clicked
事件冒泡的实际应用
假设你正在用纯 JavaScript 开发一个待办事项应用,用户可以点击待办事项来切换其完成状态。为每个待办事项添加单独的事件监听器是不合理的,因为
- 列表可能会很长。(这个过程会变得很繁琐。没错,你可以用循环来添加事件监听器,但是应用中事件监听器过多会消耗大量浏览器内存,导致应用运行缓慢。)
- 可以动态添加新的待办事项。(无法为其添加事件监听器)
我们可以通过给包含列表的父元素添加事件监听器来解决这个问题。请仔细查看以下代码的作用。
<ul class="list">
<li class="item">Wash dishes</li>
<li class="item">Walk your dog</li>
</ul>
.completed {
text-decoration: line-through;
}
const list = document.querySelector(".list");
list.addEventListener("click", e => {
e.target.classList.toggle("completed")
})
点击某个元素会切换completed该元素的类,从而为文本添加删除线。同时还会生成一个事件对象,该对象具有一个target属性。该e.target属性指向被点击的 DOM 元素,你可以添加classList和toggle切换该元素的类。
目标值与当前目标值
这是一个常见的面试题。你刚刚学到,`target` 指的是触发事件的 DOM 元素。`CurrentTarget` 指的是事件监听器正在监听的 DOM 元素。让我们在函数内部进行控制台e.target输出。e.currentTarget
const list = document.querySelector(".list");
list.addEventListener("click", e => {
console.log(e.target); // <li class="item completed">Walk your dog</li>
console.log(e.currentTarget); // <ul class="list"></ul>
e.target.classList.toggle("completed")
})
如果父元素有事件监听器,但我们阻止了子元素中的事件传播,则 currentTarget 指向阻止传播的 DOM 元素。
事件捕获
要启用此功能,请将其true作为第三个参数传递给 addEventListener 方法。
Element.addEventListener("click", function(){}, true);
这种传播方式很少使用。它不是从内到外传播,而是反转方向,从外到内传播。以下是层级结构。
窗口 -> 文档 -> HTML -> 正文 -> 目标
所以,如果你想先获取事件监听的父元素,就可以使用这个方法。我们来看前面一个例子。
<div id="div1">
<div id="div2">I am a div</div>
</div>
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
div1.addEventListener("click", function() {
console.log("div1 clicked");
}, true);
div2.addEventListener("click", function() {
console.log("div2 clicked");
});
结果
div1 clicked
div2 clicked
概括
认真倾听用户交互并正确处理是打造无 bug 应用的第一步。记住,冒泡机制就像气泡一样,从内部向上冒泡到外部;而捕获机制则是事件从内部向下冒泡的过程!感谢阅读!
文章来源:https://dev.to/shimphillip/handing-javascript-events-efficiently-with-bubble-and-capture-4ha5