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()
方法判断目标元素和委托元素是否一致,如果一致则执行方法。
优缺点
使用事件委托的好处在于:
- 避免了多次类似的事件绑定与解绑,减少内存消耗。
- 支持动态绑定,与 AJAX 配合更好。
不过事件委托也有它的局限性:
- 事件委托基于事件冒泡,
focus
、blur
等事件不支持委托。 - 如果有一层元素设置了
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原理 - 理解事件捕获、冒泡与委托