Get Started
- DOM事件
○ 标准
○ 术语
○ addEventListener
○ 取消冒泡
○ 自定义事件 - 事件委托
DOM事件
点击事件,先从这里开始研究
代码
<div class="1">
<div class="2">
<div class="3">
文字
</div>
</div>
</div>
给三个div分别添加了事件监听,当点击文字时,最先点击了哪个div,最新按调用哪个事件监听函数?
2002年,W3C发布标准
• 文档名伪DOM Level 2 Events Specification
• 规定浏览器应该同时支持两种调用顺序
• 首先按照最外层=>最里层(1->2->3)顺序看看有没有函数监听
• 然后按照最里层=>最外层(3->2->1)顺序看看有没有函数监听
• 有监听函数就调用,并提供事件信息,没有就跳过
术语
• 从外向内找监听函数,叫事件捕获
• 从内向外找监听函数,叫事件冒泡
注意:开发者可以自己选择把监听函数放在捕获阶段还是冒泡阶段。
示意图
addEventListener
• 事件绑定API
x.attachEvent('onclick', fn) // 冒泡(IE 5)
x.addEventListener('click', fn) // 捕获(网景)
x.addEventListener('onclick', fn, bool)
- 如果bool不传或为falsy
就让fn走冒泡,即当浏览器在冒泡阶段发现x有fn函数监听,就会调用fn,并提供事件信息 - 如果bool为true
就让fn走捕获,即当浏览器在捕获阶段发现x有fn函数监听,就会调用fn,并提供事件信息 - 决定走哪种方式,另一边就会按照顺序空跑。
- 示例代码:http://js.jirengu.com/pupuz/6/edit?html,css,js,output
- 示例代码笔记:事件信息e对象被传给所有监听函数,事件结束后,e对象就不存在了。若是延迟函数或者其他时候需要用到e对象时,需要copy一份到函数局部变量里使用。
target V.S. currentTarget
- 区别
e.target - 用户操作的元素
e.currentTarget - 程序员监听的元素
this是e.currentTarget,但为了不搞混最好不要用。 - 举例
div > span{文字} ,用户点击文字
e.target 就是span
e.currentTarget 就是div
一个特例
- 背景
只有一个div被监听(不考虑父子同时被监听)
fn分别在捕获阶段和冒泡阶段监听click事件
用户点击的元素就是开发者监听的 - 代码
div.addEventListener('click', f1)
div.addEventListener('click', f2,true)
答案:谁先监听谁先执行
取消冒泡
- 捕获不可以取消,但是冒泡可以
e.stopPropagation()
可以中断冒泡,浏览器不再向上走。一般用于封装某些独立的组件
不可阻止默认动作 -
有些事件不能阻止默认动作
MDN搜索scroll event,看到Bubbles和Cancelable
Bubbles的意思是该事件是否冒泡,所有冒泡都可取消
Cancelable的意思是开发者是否可以阻止默认事件
Cancelable与冒泡无关
插曲:如何阻止滚动
- scroll事件不可阻止默认动作
阻止scroll默认动作没用,因现有滚动才有滚动事件
要阻止滚动,可阻止wheel和touchstart的默认动作
注意需要找准滚动条所在元素,示例
到那时滚动条还能用,可用CSS让滚动条width: 0
::-webkit-scrollbar{width: 0 !important}
- CSS也行
使用overflow: hidden可以直接取消滚动条
但此时JS依然可以修改scrollTop - 代码
// pc端
div.addEventListener('wheel',(e) => { // 滚轮事件
e.preventDefault(); // 阻止默认事件
})
// 然后CSS设置 ::webkit-scrollbar{
// width:0;!important
//} 即可实现无滚动
// 移动端
div.addEventListener('touchstart',(e) => { // 触摸事件
e.preventDefault(); // 阻止默认事件
})
自定义事件
- 浏览器自带事件
一共100多种事件,MDN - 可以自定一个事件
- 代码
button1.addEventListener('click', ()=>{ const event = new CustomEvent('frank',{ detail:{name: 'frank', age:18}, bubbles: true, //默认不进行冒泡,所以要加上 cancelable: false }) button1.dispatchEvent(event) }) div1.addEventListener('frank', (e)=>{ console.log('事件触发了') console.log(e.detail) })
事件委托
- 场景一
要给100个按钮添加点击事件,需要监听这100个按钮的上层,等冒泡的时候判断target是不是这100个按钮中的一个 -
代码
- 场景二
监听目前不存在的元素的点击事件,也是监听上层,等点击的时候看看是不是想要监听的元素即可 -
代码
- 优点
省监听数(内存)
可以监听动态元素
封装事件委托
- 要求
写出函数on('click', '#div1', 'button', fn)
,当用户点击里面的button元素时,调用fn函数 - 代码一
判断target是否匹配'#div1'// 封装事件函数 function on(eventType, element, selector, fn){ if(!(element instanceof Element)){ // '#div'是个选择器 element = document.querySelector(element) } element.addEventListener(eventType,(e)=>{ const t = e.target if(t.matches(selector)){ fn(e) } }) }
- 代码二
递归判断,向上层找,一直到第二个参数锁给定的范围function on(eventType, element, selector, fn) { if (!(element instanceof Element)) { // '#div'是个选择器 element = document.querySelector(element) } element.addEventListener(eventType, (e) => { let el = e.target while (!el.matches(selector)) { if (element === el) { el = null break } el = el.parentNode } el && fn.call(el, e, el) }) return element }
JS支持事件吗?
- 不支持。这是DOM事件,不属于JS功能,属于浏览器提供的DOM功能
JS只是调用了DOM提供的addEventListener而已 - 如何JS支持事件?手写一个事件系统