事件机制
定义:
浏览器的事件机制指的是事件在DOM里的传递规则。
浏览器的事件传递规则大致分为3个阶段
- 捕获阶段
- 目标阶段
- 冒泡阶段
除了目标阶段,事件传播的顺序大致是这样的:
捕获阶段 => 目标阶段(先捕获还是先冒泡看对应注册事件的先后) => 冒泡阶段
在之前我们先说下注册事件的方法addEventListener,这个方法包括2个必选参数和2个可选参数,这里只说第三个参数是useCapture的情况,也就是true代表捕获阶段触发,false代表冒泡阶段触发。
先看个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
height: 100%;
width: 100%;
}
#div1 {
width: 600px;
height: 400px;
background-color: grey;
}
#div2 {
width: 400px;
height: 300px;
background-color: lightblue;
}
#div3 {
width: 300px;
height: 200px;
background-color: rgb(21, 84, 105);
}
</style>
<script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
</head>
<body>
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
window.addEventListener('click', function(e) {
console.log('window 捕获')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, true)
document.addEventListener('click', function(e) {
console.log('document 捕获')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, true)
document.documentElement.addEventListener('click', function(e) {
console.log('documentElement 捕获')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, true)
document.body.addEventListener('click', function(e) {
console.log('body 捕获')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, true)
document.getElementById('div1').addEventListener('click', function(e) {
console.log('div1 冒泡')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, false)
document.getElementById('div1').addEventListener('click', function(e) {
console.log('div1 捕获')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, true)
document.getElementById('div2').addEventListener('click', function(e) {
console.log('div2 捕获')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, true)
document.getElementById('div3').addEventListener('click', function(e) {
console.log('div2 捕获')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, true)
window.addEventListener('click', function(e) {
console.log('window 冒泡')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, false)
document.addEventListener('click', function(e) {
console.log('document 冒泡')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, false)
document.documentElement.addEventListener('click', function(e) {
console.log('documentElement 冒泡')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, false)
document.body.addEventListener('click', function(e) {
console.log('body 冒泡')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, false)
// document.getElementById('div1').addEventListener('click', function(e) {
// console.log('div1 冒泡')
// console.log('e.target', e.target)
// console.log('e.currentTarget', e.currentTarget)
// console.log('e.eventPhase', e.eventPhase)
// console.log('----------------------')
// }, false)
document.getElementById('div2').addEventListener('click', function(e) {
console.log('div2 冒泡')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, false)
document.getElementById('div3').addEventListener('click', function(e) {
console.log('div2 冒泡')
console.log('e.target', e.target)
console.log('e.currentTarget', e.currentTarget)
console.log('e.eventPhase', e.eventPhase)
console.log('----------------------')
}, false)
</script>
</body>
</html>
别看那么一大段,其实就是从最外层的元素一直到最内层分别注册click事件。
用鼠标点击div1看下打印结果:
test.html:44 window 捕获
test.html:45 e.target <div id="div1">…</div>
test.html:46 e.currentTarget Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
test.html:47 e.eventPhase 1
test.html:48 ----------------------
test.html:51 document 捕获
test.html:52 e.target <div id="div1">…</div>
test.html:53 e.currentTarget #document
test.html:54 e.eventPhase 1
test.html:55 ----------------------
test.html:58 documentElement 捕获
test.html:59 e.target <div id="div1">…</div>
test.html:60 e.currentTarget <html lang="en"><head>…</head><body>…</body></html>
test.html:61 e.eventPhase 1
test.html:62 ----------------------
test.html:65 body 捕获
test.html:66 e.target <div id="div1">…</div>
test.html:67 e.currentTarget <body>…</body>
test.html:68 e.eventPhase 1
test.html:69 ----------------------
test.html:72 div1 冒泡
test.html:73 e.target <div id="div1">…</div>
test.html:74 e.currentTarget <div id="div1">…</div>
test.html:75 e.eventPhase 2
test.html:76 ----------------------
test.html:79 div1 捕获
test.html:80 e.target <div id="div1">…</div>
test.html:81 e.currentTarget <div id="div1">…</div>
test.html:82 e.eventPhase 2
test.html:84 ----------------------
test.html:126 body 冒泡
test.html:127 e.target <div id="div1">…</div>
test.html:128 e.currentTarget <body>…</body>
test.html:129 e.eventPhase 3
test.html:130 ----------------------
test.html:119 documentElement 冒泡
test.html:120 e.target <div id="div1">…</div>
test.html:121 e.currentTarget <html lang="en"><head>…</head><body>…</body></html>
test.html:122 e.eventPhase 3
test.html:123 ----------------------
test.html:112 document 冒泡
test.html:113 e.target <div id="div1">…</div>
test.html:114 e.currentTarget #document
test.html:115 e.eventPhase 3
test.html:116 ----------------------
test.html:105 window 冒泡
test.html:106 e.target <div id="div1">…</div>
test.html:107 e.currentTarget Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
test.html:108 e.eventPhase 3
test.html:109 ----------------------
这里注意的是可通过stopPropagation阻止事件传播,preventDefault阻止默认事件。e.target代表触发事件的对象,e.currentTarget代表注册事件的对象。
事件代理
定义:
事件代理就是指某个DOM的事件通过冒泡转交给其他DOM上注册的相同事件的回调去处理。 例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body,
html {
height: 100%;
width: 100%;
}
#div1 {
width: 600px;
height: 400px;
background-color: grey;
}
#div2 {
width: 400px;
height: 300px;
background-color: lightblue;
}
#div3 {
width: 300px;
height: 200px;
background-color: rgb(21, 84, 105);
}
</style>
<script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
</head>
<body>
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
<script>
document.getElementById("div1").addEventListener('click', function(e) {
console.log(`hi i am ${e.target.id}`)
}, false)
</script>
</body>
</html>
分别点击div1, div2, div3,查看打印结果:
test.html:44 hi i am div1
test.html:44 hi i am div2
test.html:44 hi i am div3
事件代理的优点:
- 减少内存占用
- 不需要给子节点注销事件