事件冒泡,事件捕获,事件委托
# 事件机制说起
事件触发三阶段 捕获阶段,目标阶段,冒泡阶段
- 捕获阶段:
事件从根节点流向目标节点,途中流经各个 DOM 节点,在各个节点上触发捕获事件
,直到达到目标节点。
捕获阶段的主要任务是建立传播路经,在冒泡阶段根据这个路经回溯到文档根节点
目标阶段 事件到达目标节点时,就到了目标阶段,事件在目标节点上被触发
冒泡阶段(从哪里来,回哪里去) 事件在目标节点上触发后,不会终止,一层层向上冒,回溯到根节点。
小 demo 演示下:
(这是一张图片,demo 的代码在下面)
思考:
- 点击
grandson
范围,会触发到什么? - 点击
children
范围,会触发到什么? - 点击
parent
范围,会触发到什么?
查看 demo 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
#grandson {
width: 100px;
height: 100px;
background-color: #00f;
}
#children {
width: 150px;
height: 150px;
background-color: #8ac28a;
}
#parent {
width: 200px;
height: 200px;
background-color: green;
}
div {
color: #fff;
text-align: center;
margin: 0 auto;
}
</style>
<body>
<div id="parent">
parent
<div id="children">
children
<div id="grandson">
grandson
</div>
</div>
</div>
</body>
<script>
let parent = document.getElementById('parent')
let children = document.getElementById('children')
let grandson = document.getElementById('grandson')
parent.addEventListener('click', function(e) {
console.log('click - parent', e)
})
children.addEventListener('click', function(e) {
console.log('click - children', e)
})
grandson.addEventListener('click', function(e) {
console.log('click - grandson', e)
})
</script>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
- 点击
grandson
。首先打印的是grandson
,其次children
,最后parent
其余同理。这也证实了事件触发的 3 个阶段分别是捕获
->目标
->冒泡
---可以在看下 e
打印的内容,3 个e
都是一样的内容。只看比较有价值的部分。
我这里点击的的是grandson
,打印如下:
path
代表冒泡的路径,一直从grandson
重新回到document
到window
target
就是我们点击的目标节点了
那会不会是因为 3 个元素重叠了(图层重叠),所以才会触发到这样的?
那 div 结构改变一下:
刚才的 html 结构不变,只是多加了一层定位。然后实验结果是一样的,依旧是 grandson
,其次children
,最后 parent
。只要节点是父子节点关系,就都会触发到对应的事件
# 事件委托
原理:
利用
事件冒泡机制
实现的优点:
- 可以为动态添加的 dom 绑定事件(动态 dom 就是通过 JS,后期添加的 dom 元素)
- 只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有元素都绑定事件,减少内存空间占用,提升性能 动态新增的元素无需重新绑定事件
注意事项:
事件委托的实现依靠事件冒泡,因此不支持事件冒泡的事件就不适合用事件委托。
用到刚才修改过定位的 demo、现在为 position 注册点击事件
<body>
<div id="position">
<div id="parent">
parent
<div id="children">
children
<div id="grandson">
grandson
</div>
</div>
</div>
</div>
</body>
<script>
var position = document.getElementById('position')
position.onclick = function(e) {
var e = event || window.event
var target = e.target || e.srcElement
console.log(e.target.nodeName, e.target.id)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果采用了事件委托
的方式。触发的节点就只有一个
!就是点击的节点
不过在 path
中还是可以看到父节点,因为这是在捕获事件
就已经记录下了
# 阻止事件冒泡
w3c 的方法是 e.stopPropagation()
,IE 则是使用 e.cancelBubble = true
。
//阻止冒泡行为
function stopBubble(e) {
//如果提供了事件对象,则这是一个非IE浏览器
if (e && e.stopPropagation) {
//因此它支持W3C的stopPropagation()方法
e.stopPropagation()
} else {
//否则,我们需要使用IE的方式来取消事件冒泡
window.event.cancelBubble = true
}
}
document.getElementById('childred').onclick = function(e) {
stopBubble(e) // 这样就可以阻止事件冒泡了
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 取消默认事件
什么是默认事件?和事件冒泡什么区别?
既然是说默认行为,当然是元素必须有默认行为
才能被取消,如果元素本身就没有默认行为,调用当然就无效了
哪些元素有默认行为呢?
- a 标签
<input type="submit">
- ... 不用写 JS 就会有交互的,我们都称为:
默认行为
阻止默认行为:
w3c 的方法是 e.preventDefault(),IE 则是使用 e.returnValue = false;
//阻止冒泡行为
function stopDefault(e) {
if (e && e.preventDefault) {
e.preventDefault()
} else {
window.event.returnValue = true
}
}
document.getElementByTagName('a').onclick = function(e) {
stopDefault(e)
}
2
3
4
5
6
7
8
9
10
11
12
如何判断元素是否已经使用了 e.preventDefault()
我们可以在事件对象中使用
event.defaultPrevented
属性。它返回一个布尔值
用来表明是否在特定元素中调用了 event.preventDefault()。
# JQ 阻止事件冒泡和默认行为
$('#对应节点').on('click', function() {
return false
})
2
3