2002 年,W3C 发布标准
- 文档名为 DOM Level 2 Events Specification
- 规定浏览器应该同时支持两种调用顺序
- 首先按
爷爷=>爸爸=>儿子
顺序看有没有函数监听 - 然后按
儿子=>爸爸=>爷爷
顺序看有没有函数监听 - 有监听函数就调用,并提供事件信息,没有就跳过
术语
- 从外向内找监听函数,叫
事件捕获
- 从内向外找监听函数,叫
事件冒泡
开发者自己选择把调用放在 捕获阶段 还是放在 冒泡阶段
图片来源:饥人谷
addEventListener
事件绑定 API
- IE5*: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,并提供事件信息
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 的意思是开发者是否可以取消冒泡
插曲:如何阻止滚动
scroll 事件不可取消冒泡
- 阻止 scroll 默认动作没用,因先有滚动才有滚动事件
- 要阻止滚动,可阻止 wheel 和 touchstart 的默认动作
- 注意你需要找准滚动条所在的元素
- 但是滚动条还能用,可用 CSS 让滚动条 display: none
CSS 也行
- 使用 overflow: hidden 可以直接取消滚动条
- 但此时 JS 依然可以修改 scrollTop
禁止滚动
// 滚轮默认效果
x.addEventListener('wheel',(e)=>{
e.preventDefault()
})
// 取消手机滑动
x.addEventListener('touchstart', (e)=>{
e.preventDefault()
})
取消滚动条(把滚动条宽度变成0)
::-webkit-scrollbar{width: 0 !important}
自定义事件
浏览器自带事件
- 一共100多种事件, 可在 MDN 查看
定义事件
button1.addEventListener('click', ()=>{
const event = new CustomEvent('ivan', {
detail: {name:'ivan', age:18}
bubbles: true
cancelable: false
}) // 创建一个事件
button1.diapatchEvent(event)
})
div1.addEventListener('ivan', (e)=>{
console.log('事件触发了')
console.log(e.detail)
})
事件委托
优点
- 省监听数(内存)
- 可以监听动态元素
封装事件委托
setTimeout(()=>{
const button = document.createElement('button')
button.textContent = 'click 1'
div1.appendChild(button)
}, 1000)
on('click', '#div1', 'button', ()=>{
console.log('button 被点击了')
})
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)
}
})
}