一、简介
事件流是一个事件沿着特定数据结构传播的过程。
冒泡和
捕获是事件流在DOM中两种不同的
传播方法事件流有三个阶段
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
事件捕获
事件捕获(
event capturing
):通俗的理解就是,
当鼠标点击或者触发dom
事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件 事件冒泡
事件冒泡(
dubbed bubbling
):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点
示意图
二、W3C事件模型
因为有捕获和冒泡两种传播方式,W3C制定了一个标准可以让我们自己选择使用哪种传播方式
addEventListener('click',fn,?)
第三个参数
?
是一个
bool
值,决定使用捕获或者冒泡
举个例子
<div>
<p>点我看效果</p>
</div>
当你
addEventListener
函数
第三个参数为true
时就表示你使用的是
事件捕获。父级元素先触发,子级元素后触发,也就是说
div
先触发,
p
后触发。
当你
addEventListener
函数
第三个参数为空或为false
时就表示你使用的是
事件冒泡。子级元素先触发,父级元素后触发,也就是说
p
先触发,
div
后触发。
三、Event对象常见的应用
event. preventDefault()阻止默认事件
如果调用这个方法,默认事件行为将不再触发。什么是默认事件呢?例如表单一点击提交按钮(
submit
)跳转页面、
a
标签默认页面跳转或是锚点定位等。
很多时候我们使用
a
标签仅仅是想当做一个普通的按钮,点击实现一个功能,不想页面跳转,也不想锚点定位。
<a id='a' href='https://baidu.com'>百度</a>
<script>
a.addEventListener("click", function(e){
e.preventDefault();
});
</script>
我们给
a
添加点击事件,当用户点击
百度
就阻止
a
标签的默认事件,所以点击后不会有跳转
event.stopPropagation()阻止事件冒泡
上面提到事件冒泡阶段是指事件从目标节点自下而上向
window
对象传播,当事件使用
event.stopPropagation()
方法将阻止事件冒泡到父元素,阻止任何父事件处理程序被执行
<div id="hi" style="border: 1px solid red; width: 100px; height: 100px;">
<div id="hello" style="border: 1px solid red; width: 50px; height: 50px;"></div>
</div>
<script>
hi.addEventListener("click", function(){
console.log('hi')
});
hello.addEventListener("click", function(e){
console.log('hello')
e.stopPropagation()
});
</script>
因为在子元素点击事件中使用了
event.stopPropagation()
阻止冒泡事件,所以最终只打印出了目标元素
'hello'
,父元素的
'hi'
并没有被打印出
是否可以阻止冒泡
并不是所有事件都可以阻止冒泡的,具体可以
MDN
搜索
scroll event
,看到
Bubbles
和
Cancelable
Bubbles
的意思是该事件是否冒泡
Cancelable
的意思是开发者是否可以阻止冒泡
event.target & event.currentTarget
-
指向事件触发的元素e.target
-
指向事件绑定的元素e.currentTarget
四、事件委托
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件委托
优点
减少内存消耗,提高性能
假设有一个
div
,
div
里有大量的
button
,我们需要在点击每个
button
的时候响应一个事件
<div id="container">
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
</div>
如果给每个
button
都绑定一个函数,那对于内存消耗是非常大的。借助事件代理,我们只需要给父容器
div
绑定方法即可,这样不管点击的是哪一个后代元素,都会根据冒泡传播的传递机制,把容器的
click
行为触发,然后把对应的方法执行,根据事件源,我们可以知道点击的是谁,从而完成不同的事。
动态绑定事件
在很多时候,我们需要通过用户操作动态的增删子元素,如果一开始给每个子元素绑定事件,那么在列表发生变化时,就需要重新给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件委托就会省去很多这样麻烦。
实现
接下来我们来实现上例中父层元素
#container
下的
button
元素的事件委托到它的父层元素上:
<div id="container">
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
</div>
<script>
container.addEventListener('click', function (e) {
//把目标元素赋给t
let t = e.target
// 判断是否匹配目标元素
if (t.nodeName.toLocaleLowerCase() === 'button') {
console.log('我是:' + t.textContent);
}
});
</script>
这样当点击每个
button
都触发事件打印出对应元素的文本内容