本篇博客是作为个人自学记录,如有不足之处,请批评指正。
前言
代码:
<div class = 爷爷>
<div class = 爸爸>
<div class = 儿子>
文字
</div>
</div>
</div>
<!-- 分别为三个 div 添加事件监听 fnYe/ fnBa/ dnEr -->
请问1:点击了谁?
- 点击文字,算不算点击儿子?
- 点击文字,算不算点击爸爸?
- 点击文字,算不算点击爷爷?
请问2:调用顺序
- 点击文字,最先调用 fnYe/ fnBa/ dnEr 中哪一个函数?
- 答案:都行
- IE5 认为先调用fnEr,网景认为先调用 fnYe
- 最后 W3C 发布了标准
W3C 规定浏览器应该同时支持两种调用顺序:
首先:按 爷爷 → 爸爸 → 儿子 顺序看有没有函数监听(从外向内)
然后:按 儿子 → 爸爸 → 爷爷 顺序看有没有函数监听(从内向外)
有监听函数就调用,并提供事件信息,没有就调过
事件捕获和事件冒泡
- 从外向内 找监听函数,叫 事件捕获
- 从内向外 找监听函数,叫 事件冒泡
- 先捕获,后冒泡
W3C 事件模型
addEventListener
时间绑定 API
// IE 5:
.attachEvent('onclick', fn) //冒泡
// 网景:
.addEventListener('click', fn) // 捕获
// W3C:
.addEventListener('click', fn, bool)
// 如果 bool 不传或为 falsy 就走冒泡
// 如果 bool 为 true 就走捕获
小结一
疑问:
- 儿子被点击了,算不算点击爸爸? 答案:算
- 先调用爸爸的函数还是先调用儿子的函数? 答案:根据W3C事件模型,先捕获再冒泡,先从爸爸到儿子,再从儿子到爸爸
捕获与冒泡:
- 捕获:先调用爸爸的监听函数
- 冒泡:先调用儿子的监听函数
W3C 事件模型
- 先捕获,再冒泡?(但是可以阻止冒泡)
- 注意事件被传给所有监听函数
- 事件结束后,对象就不存在了
target 和 currentTarget
区别
- target 是用户操作的元素
- currentTarget 是程序员监听的元素
- this 是 currentTarget,不推荐使用它
举例
<div>
<span>
文字
</span>
</div>
- 用户点击文字
- target 就是 span
- currentTarget 就是 div
特例
- 当只有一个 div 被监听(不考虑父子同时被监听)
- fn 分别同时在捕获阶段和冒泡阶段监听 click 事件
- 谁先监听谁先执行
阻止冒泡
捕获是不能取消的,但是冒泡可以
e.stopPropagation
(e)=>{
e.stopPropagation()
}
一般用于封装某些独立的组件
不可取消冒泡
scroll 事件不可取消冒泡
小结二
target 和 currentTarget
- 一个是用户点击的,一个是开发者监听的
阻止冒泡
- e.stopPropagation()
事件的特性
- Bubbles 表示是否冒泡
- Cancelable 表示是否支持开发者取消冒泡
- 如:scroll 不支持取消冒泡
如何禁用滚动
- 取消特定元素的 wheel 和 touchstart 的默认动作
事件委托
场景一:
- 你要给100个按钮添加点击事件,咋办?
- 答案:监听这100个按钮的祖先,等冒泡的时候判断 target 是不是这100个按钮中的一个
场景二:
- 你要监听目前不存在的元素的点击事件,咋办?
- 答案:监听祖先,等点击的时候看看是不是我想要监听的元素即可
事件委托其实很好理解
- 利用事件冒泡的原理,把事件监听放在祖先元素(如父元素、爷爷元素)上。
优点
- 节约监听数量
- 可以监听动态生成的元素
封装事件委托
function on(eventType, element, selector, fn){
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType, (e) => {
const t = e.target
if(t.matches(selector)){
fn(e)
}
})
}
// 给一个元素添加监听,然后看当前的 target 是不是满足这个 selector,如果满足就调用函数,不满足的话就不调用