你可以看到
要说的话
用例子带你逐步弄懂捕获与冒泡,事件与事件委托。
–资料来源于饥人谷
了解捕获与冒泡
往下代码都以点击事件作为例
先一步一步了解。
上代码
- 代码-HTML
<div class=爷爷>
<div class=爸爸>
<div class=儿子>
文字
</div>
</div>
</div>
以上代码即.爷爷>.爸爸>.儿子
给三个div分别添加事件监听 fnYe / fnBa / fnEr。点击谁监听谁
- 针对上面代码 提问1:点击了谁
点击文字,算不算点击儿子?
点击文字,算不算点击爸爸?
点击文字,算不算点击爷爷?
-答案:都算
- 提问2:调用顺序呢
点击文字,最先调用fnYe /fnBa / fnEr中的哪一个函数?
答案:都行。看浏览器怎么实现的。
IE5认为先调 fnEr,网景认为先调fnYe,然后掐上了,最后闹到了W3C
和事佬W3C
- 2002年,W3C发布标准
文档名为DOM Level 2 Events Specification
规定浏览器应该同时支持两种调用顺序
首先按爷爷=>爸爸=>儿子顺序看有没有函数监听
然后按儿子=>爸爸=>爷爷顺序看有没有函数监听
有监听函数就调用,并提供事件信息,没有就跳过
相必你已经大致了解了捕获与冒泡捕获与冒泡
- 术语
从外向内找监听函数,叫事件捕获
从内向外找监听函数,叫事件冒泡
疑问:从外到内再从内到外,那岂不是fnYe / fnBa / fnEr都调用两次?
非也!开发者自己选择把fnYe放在捕获阶段还是放在冒泡阶段
解释:
蓝色td被点击了,要一个个问,先问window:你有没有函数要调用的,因为你的后代的后代…被点击了。window说:我没有函数可以管。然后再像这样依次调用下来,直到问到td。这个过程走完了后会有个目标阶段,就是在td这里判断一下是否需要冒泡。td说:我需要冒。那就冒回去吧,然后从td到window再走一遍,再把所有函数调用一遍(有就调,没有就不调)
addEventListener 监听事件
- 事件绑定API
IE 5*: baba.attachEvent(‘onclick’, fn)//冒泡
网景: baba.addEventListener( ‘click’, fn)//捕获
W3C: baba.addEventListener(‘click’, fn, bool)
- 如果bool不传或为falsy
就让fn走冒泡,即当浏览器在冒泡阶段发现baba有fn 监听函数,就会调用fn,并提供事件信息
- 如果bool为true
就让fn走捕获,即当浏览器在捕获阶段发现 baba有fn 监听函数,就会调用fn ,并提供事件信息
你可以选择把fn放在哪边
- 简化后的代码
- 代码图解
小结
- 两个疑问
儿子被点击了,算不算点击老子?算
那么先调用老子的函数还是先调用儿子的函数?不确定,IE先调用儿子,firefox就先调用老子,最终W3C说我两个都支持,我先从老子到儿子,在从儿子到老子,这下你们满意了吧。
- 捕获与冒泡
捕获说先调用爸爸的监听函数,再调用儿子的监听函数
冒泡说先调用儿子的监听函数,再调用爸爸的监听函数
W3C事件模型
先捕获(先爸爸=>儿子) 再冒泡?(再儿子=>爸爸)(可以阻止冒泡)
注意e对象被传给所有监听函数
事件结束后,e对象就不存在了
说不存在是不想纠结于e。在事件结束后,不推荐大家继续用 e,因为 e 会被浏览器悄悄改变,要用的话你应该提前把属性复制到自己的变量里,比如示例代码里的 const t = e.currentTarget 就是把 currentTarget 的引用复制到 t。
target v.s. currentTarget
-区别
e.target- 用户操作的元素
e.currentTarget-程序员监听的元素
this是e.currentTarget,我个人不推荐使用它
-举例
div > span{文字},用户点击文字后
e.target就是span
e.currentTarget就是div
一个特例
- 背景
只有一个div被监听(不考虑父子同时被监听)
fn 分别在捕获阶段和冒泡阶段监听click事件
用户点击的元素就是开发者监听的
代码
div.addEventLisenter('click', f1)
div.addEventLisenter('click', f2, true)
- 请问
f1先执行还是f2先执行?
如果把两行调换位置后,请问哪个先执行?
错误答案:f2先执行
正确答案:谁先监听谁先执行,没有父子
总结:这是一个特例
取消冒泡
- 捕获不可取消,但冒泡可以
e.stopPropagation()
可中断冒泡,浏览器不再向上走。通俗来说:有人打我,我自己解决,别告诉我老子
一般用于封装某些独立的组件
不可阻止默认动作
- 有些事件不能阻止默认动作
MDN搜索scroll event,看到Bubbles和 Cancelable
Bubbles的意思是该事件是否冒泡,所有冒泡都可取消
Cancelable的意思是开发者是否可以阻止默认事件
Cancelable与冒泡无关
推荐看MDN英文版,中文版内容不全
- 如何阻止滚动
scroll 事件不可阻止默认动作
阻止scroll默认动作没用,因先有滚动才有滚动事件
要阻止滚动,可阻止 wheel和 touchstart的默认动作
注意你需要找准滚动条所在的元素,示例
但是滚动条还能用,可用CSS让滚动条width: 0 (::-webkit-scrollbar { width: 0 !important }
)
- CSS也行
使用overflow: hidden可以直接取消滚动条
但此时JS依然可以修改scrollTop
小结
- target和currentTarget
一个是用户点击的,一个是开发者监听的
- 取消冒泡
e.stopPropagation()
- 事件的特性
Bubbles表示是否冒泡
Cancelable表示是否支持开发者取消冒
如scroll不支持取消冒泡
- 如何禁用滚动
取消特定元素的wheel和touchstart的默认动作
自定义事件
- 浏览器自带事件
一共100多种事件,列表在MDN 上
- 提问
开发者能不能在自带事件之外,自定义一个事件
答案:可以,见示例
事件委托
委托一个元素监听我本该监听的东西。就是把事件监听放在祖先元素(如父元素、爷爷元素)上。
针对特定场景的事件委托
- 场景一
你要给100个按钮添加点击事件,咋办?
答:监听这100个按钮的祖先,等冒泡的时候判断target是不是这100个按钮中的一个,代码示例
- 场景二
你要监听目前不存在的元素的点击事件,咋办?
答:监听祖先,等点击的时候看看是不是我想要监听的元素即可,代码示例
- 优点
省监听数(内存)
可以监听动态元素
封装事件委托
- 要求
写出这样一个函数on(‘click’, ‘#testDiv’, ‘li’, fn)
注意:先搞清楚输入(封装的四个参数,事件绑定、元素选择器 Element.matches 、事件 event 对象,某些函数)在搞清楚输出
当用户点击#testDiv里的li元素时,调用fn函数
要求用到事件委托
- 答案一
判断target是否匹配’li’等选择器,代码示例 - 答案二
递归判断target / target的爸爸/target的爷爷。(当前元素不匹配button就看他的爸爸匹配不,只要我的祖先中某一项是button,说明点击的就是button),代码示例 - 整合进jQuery
有兴趣可以自己实现$(’#xxx’).on(‘click’, ‘li’, fn)
JS支持事件吗
- 答
不支持。本次讲的DOM事件不属于JS的功能,是浏览器提供的 DOM的功能
JS只是调用了DOM提供的addEventListener而已
–continue
这是小白的学习之旅
既然看到这了都那就