事件冒泡、事件捕获和事件委托
事件流
概念
:事件流指从页面中接收事件的顺序,有冒泡流和捕获流。
DOM二级事件规定事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
从网上找到一张图片:
事件冒泡和事件捕获
addEventListener
最后一个参数,为true则代表使用事件捕获模式 ,false则表示使用事件冒泡模式。默认为false。
冒泡模式
<body>
<div id="parent">
<button id="child">click me</button>
</div>
<script>
const parent = document.getElementById('parent')
const child = document.getElementById('child')
child.addEventListener('click', (e) => {
console.log('1 click child');
})
parent.addEventListener('click', (e) => {
console.log('2 click parent');
})
document.body.addEventListener('click', (e) => {
console.log('3 click body');
})
document.addEventListener('click', (e) => {
console.log('4 click document');
})
window.addEventListener('click', (e) => {
console.log('5 click window');
})
</script>
</body>
捕获模式
<body>
<div id="parent">
<button id="child">click me</button>
</div>
<script>
const parent = document.getElementById('parent')
const child = document.getElementById('child')
child.addEventListener('click', (e) => {
console.log('1 click child');
}, true)
parent.addEventListener('click', (e) => {
console.log('2 click parent');
}, true)
document.body.addEventListener('click', (e) => {
console.log('3 click body');
}, true)
document.addEventListener('click', (e) => {
console.log('4 click document');
}, true)
window.addEventListener('click', (e) => {
console.log('5 click window');
}, true)
</script>
</body>
阻止事件冒泡
stopPropagation()
在上面例子中针对第一个button
按钮加上e.stopPropagation()
进行阻止冒泡。
child.addEventListener('click', (e) => {
console.log('1 click child');
e.stopPropagation()
})
事件委托
每当将事件处理程序制定给元素时,运行中的浏览器代码与支持页面交互的JS代码之间就会建立一个连接,而这种连接越多,页面执行起来就越慢。
因为冒泡机制,比如既然点击子元素,也会触发父元素的点击事件,那我们完全可以将子元素的事件要做的事写到父元素的事件里,也就是将子元素的事件处理程序写到父元素的事件处理程序中,这就是事件委托;利用事件委托,只指定一个事件处理程序,就可以管理某一个类型的所有事件;
无限下拉的图片列表,如何监听每个图片的点击
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
img {
width: 300px;
}
</style>
</head>
<body>
<div id="app">
<div class="img">
<a href="#">
<img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
</a>
</div>
<div class="img">
<a href="#">
<img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
</a>
</div>
<div class="img">
<a href="#">
<img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
</a>
</div>
<button>加载更多</button>
</div>
</body>
<script>
const app = document.getElementById('app')
app.addEventListener('click', (e) => {
if (e.target.matches('img')) {
console.log(e.target);
}
})
</script>
</html>
思考一下,这样写就是我们需要每次写代理和普通绑定不一样的写法,我们可不可以封装一个通用的绑定函数来实现,函数的回调里面this指向我们指定的目标元素。
面试题:通用事件绑定(bindEvent)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
img {
width: 300px;
}
</style>
</head>
<body>
<div id="app">
<div class="img">
<a href="#">
<img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
</a>
</div>
<div class="img">
<a href="#">
<img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
</a>
</div>
<div class="img">
<a href="#">
<img src="http://imgoss.cnu.cc/2201/25/736031deeccb3fbfa78a771040121ce1.jpg?x-oss-process=style/content" alt="">
</a>
</div>
<button id="btn">加载更多</button>
</div>
</body>
<script>
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if (selector) {
// 代理绑定 matches正则匹配
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}
const btn = document.getElementById('btn')
bindEvent(btn, 'click', function (event) {
console.log(this.innerHTML);
})
const app = document.getElementById('app')
// 这里注意回调函数需要使用ES5写法,下面的this才指向目标元素
bindEvent(app, 'click', 'img', function (event) {
console.log(this.src);
})
</script>
</html>