DOM事件与事件委托
1.事件是什么?
通俗理解,事件是用户或者浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)按钮等,这里的click就是事件的名称。
- JS与html之间的交互是通过事件实现的,DOM支持大量的事件。
- JavaScript 创建动态页面
- 事件是可以被 JavaScript 侦测到的行为。
- 网页中的每个元素都可以产生某些可以触发 JavaScript 函数或程序的事件。
- 触发事件的对象称为事件发送者;接收事件的对象称为事件接收者。
- 比如说,当用户单击按钮或者提交表单数据时,就发生一个鼠标单击(onclick)事件,需要浏览器做出处理,返回给用户一个结果。

2.事件对象
- 每次触发DOM事件时会产生一个事件对象(也称event对象)
- 参数e接收事件对象
- 事件对象也有很多属性和方法,其中target属性是获取触发事件对象的目标,也就是绑定事件的元素
- e.target表示该DOM元素,然后在获取其相应的属性值。
- this会冒泡。e.target不冒泡就是指向事件触发的dom。
event.target.nodeName //获取事件触发元素标签名(li,p,div,img,button…)
event.target.id //获取事件触发元素id
event.target.className //获取事件触发元素classname
event.target.innerHTML //获取事件触发元素的内容(li)
3.事件流
- DOM是个树形结构,当我们在页面上单击一个按钮,页面上哪些元素会触发这个事件,是发生在这个按钮上,还是这个按钮的容器元素(我们说父元素)也会触发这个点击事件呢?
- 这就牵扯到事件流,从上面的思考,我们知道它描述的是事件触发顺序,那上文中是按钮和其容器元素都触发吗,它们谁先触发呢?这可不是确定的,得看是哪种类型的事件流了。
- 点击事件
<div class="爷爷">
<div class="爸爸">
<div class="儿子">
文字
</div>
</div>
</div>
- 给三个div分别添加时间监听 fnYe/fnBa/fnEr
- 提问1:点击了谁?
1.点击文字,算不算点击儿子?
2.点击文字,算不算点击爸爸?
3.点击文字,算不算点击爷爷?
答:都算 - 提问2:调用顺序
点击文字,最先调用fnYe/fnBa/fnEr哪个函数?
答:都行(对于顺序IE5和网景有争论,由W3C发布规矩)
4.调用顺序
4.1 W3C发布标准(函数调用顺序)
- 文档明为DOM Level 2 Event Specifition
- 规定浏览器应该支持两种调用顺序
- 首先按 爷爷=>爸爸=>儿子 顺序看有没有函数监听
- 然后按 儿子=>爸爸=>爷爷 顺序看有没有函数监听
- 先 从外到内 再 从内到外
- 有监听函数就调用,并提供时间信息,没有就跳过
4.2 术语
- 从外向内找监听函数,叫事件捕获
- 从内向外找监听函数,叫事件冒泡
事件冒泡比较重要,是IE发明的
4.3 提问:那岂不是 fnYe / fnBa / fnEr 都调用两次?非也!
解决:开发者自己选择把监听函数放到捕获阶段还是冒泡阶段
- 点击td --> 从 window 依次问下来,是否有事件监听函数要执行 --> 到达td,问它是否冒泡,答要冒泡 --> 从 td再次问上去是否有监听函数要执行

5.addEventListener(事件绑定API)
- 事件绑定API
IE5*:baba.attachEvent('onclick',fn)//冒泡
网景:baba.addEventListener('click',fn)//捕获
W3C:baba.addEventListener('click',fn,bool)
——W3C综合了两者的写法
——bool参数:用来选择是冒泡顺序(false或不填)还是捕获顺序(true)
——如果不填bool就选择IE的冒泡,W3C默默支持IE,因为一般不填写 - 如果 bool 不填或为 falsy(类似false的值)
就让 fn 走冒泡,即当浏览器在冒泡阶段发现 baba 有 fn 监听函数,就会调用 fn,并提供时间信息(W3C默认偏向IE,因为经常默认不填bool) - 如果 bool 为 true
就让 fn 走捕获,即当浏览器在捕获阶段发现 baba 有 fn 监听函数,就会调用 fn,并提供时间信息 - 注意:捕获和冒泡都是一定要走的,只是开发者选择在哪边的时候执行监听函数
6.案例
- 代码实现:颜色从内往外依次出现
原理是用 x 设置了背景透明,然后依次删掉每个level的x - JS渲染是一个很快的过程,每一个间隔的操作只相差很短的时间
- 背景透明(CSS)
.x{background: transparent}; - fn的参数e只在点击的一瞬间存在,点击完了之后参数就不存在了
- 解决:const 一个变量 t,令 t=e.currentTarget
这样,即使点击一瞬间e消失了,t仍然存在 - 如果几个level的setTimeout时间都是一样是1000的话,就相当于在1s后添加了7个闹钟,想要依次响起就令为 n*1000 然后 n+=1
- 如果没有传入 bool 参数,则默认冒泡顺序,也就是执行顺序是从内往外
- 如果想要捕获的过程:则每个都添加一个 true
- 想要两个一起看,则绑定两个事件,则一共14个监听函数
先是捕获(从外到内),再是冒泡(从内到外)
let n=1
level1.addEventListener('click',(e)=>{
const t = e.currentTarget
setTimeout(()=>{
t.classList.remove('x')//删掉x
}, n*1000)
})//没有加 bool 参数默认为冒泡顺序,从里向外
level1.addEventListener('click',(e)=>{
const t = e.currentTarget
setTimeout(()=>{
t.classList.add('x')//添加上刚刚删掉的x
},n*1000)
},true)//捕获顺序
- 代码优化
将重复的代码取一个名字。(比如:给个函数,然后调用它即可)
let n=1
const fn = (e)=>{
const t = e.currentTarget
setTimeout(()=>{
t.classList.remove('x')//删掉x
}, n*1000)
}
const fm = (e)=>{
const t = e.currentTarget
setTimeout(()=>{
t.classList.add('x')//添加上刚刚删掉的x
},n*1000)
}
level1.addEventListener('click',fn)
level1.addEventListener('click',fm,ture)
- 对象e
1.事件产生对象e只在事件发生的那一瞬间存在,事件结束后自动消亡
2.则使用一个变量将其记录下来,e消亡了,但我的变量中的还在
——const t = e.currentTarget
6.1 代码图解
- 每次点击都会从 window ~ 文字,然后从 文字 ~ window
- 进入的时候发现 level1 上面有个函数,就执行,从f1-f7,然后从f7~f1
- 可以每个上面都绑定同一个函数

7.小结
7.1两个疑问
- 儿子被点击了,算不算点击了老子?
答:算,只要div里面任何一部分被点击了,这个div就算被点击了 - 那么先调用的老子的函数还是先调用儿子的函数?
答:不一定
7.2 捕获与冒泡
- 捕获说:先调用爸爸的监听函数
- 冒泡说:先调用儿子的监听函数
7.3 W3C 事件模型
- 先捕获(先爸爸=>儿子),再冒泡(再儿子=>爸爸)
注意:后面可以阻止冒泡 - 注意e对象被传给所有监听函数
事件中会有一个e的对象,这个对象会被传给所有的监听函数 - 事件结束后,e对象就不存在了
8.target V.S. currentTarget
- 它们可能相同,也可能不同
8.1 区别
- e.target - 用户在操作的元素
e对象的属性 target - e.currentTarget - 程序员监听的函数
- this是 e.currentTarget ,监听代码里不推荐使用它
8.2 举例
- div > span{文字},用户点击文字
- e.taeget 就是span
因为用户点的是 span 上的文字 - e.currentTarget 就是div
程序员监听的是div,div.onclick
9.一个特例
- 只有一个 div 被监听,也就是监听的元素也就是用户点击的元素
- fn 分别在捕获阶段和冒泡阶段监听click事件
- 没有爸爸和儿子的包含关系,那么谁先监听谁就先执行
level7.addEventListener('click',()=>{
console.log(2)
},true)//捕获
level7.addEventListener('click',()=>{
console.log(1)
})//冒泡
//打印出:2 1
10.取消冒泡
- 捕获不能取消,但是冒泡可以
- e.stopPropagation()可以中断冒泡,浏览器不再往上走
- 通俗来说:有人打我,我自己解决,别告诉我老子
- 一般用于封装独立组件
level4.addEventListener('click',(e)=>{
e.stopPropagation()
fm(e)
})
11.不可取消冒泡
- 有些事件不可(不支持)取消冒泡
- MDN 搜索 scroll event(滚动事件),看到 Bubbles 和 Cancelable
- Bubbles 的意思是该事件是否冒泡
- Cancelable 的意思是开发者是否可以取消冒泡


12.如何阻止滚动
12.1 scroll 事件不可取消冒泡
- 阻止 scroll 默认动作没用,因为现有滚动才有滚动事件
- 要阻止滚动,可以阻止 wheel(滚轮滚动) 和 touchstart(手机触屏) 的默认动作
- 注意你需要找准滚动条所在的元素
要找到能覆盖整个区域的元素 - 但是滚动条鼠标点击还能用,可用CSS让滚动条 width: 0
12.2 CSS也行
- 使用 overflow: hidden 可以直接取消滚动条
- 但此时 JS 依然可以修改 scrollTop
::-webkit-scrollbar { width:0 !important}
x.addEventListener('wheel',(e)=>{//x为body的id
e.preventDfault()
})
x.addEventListener('touchstart',(e)=>{
e.preventDfault()
})
13 小结
- target 和 currentTarget
一个是用户点击的,一个是开发者监听的 - 取消冒泡
e.stopPropagation() - 阻止默认动作
e.preventDefault() - 事件的特性
Bubbles 表示是否冒泡
Cancelable 表示是否支持开发者取消冒泡
如 scroll 不支持冒泡 - 如何禁用滚动
取消特定元素的 wheel 和 touchstart 的默认动作
而不是阻止冒泡
版权声明:本文为qq_40282016原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。