什么是 DOM 事件
- DOM 事件是浏览器的一个功能,是浏览器或者用户针对页面做出的某些动作,比如点击,鼠标移动,键盘输入等。
- DOM 事件是用户与页面进行交互的核心,当事件触发时,可以绑定一个或多个事件处理函数,来完成我们要实现的功能。
DOM 事件模型
DOM 事件模型分为两种,事件冒泡和事件捕获。当某一事件触发时,先执行捕获再执行冒泡。
addEventListener:绑定事件的监听函数
element.addEventListener(eventType, fn, bool)
- 当不传bool或为falsy值时,函数将会被放在冒泡阶段执行,并返回事件信息
- 当bool为true时,函数将会被放在捕获阶段执行,并返回事件信息
示意图:
事件冒泡
当子元素的事件类型触发时,所有父元素上绑定同类型事件均会从内到外层层触发。
事件冒泡
当子元素的事件类型触发时,所有父元素上绑定同类型事件均会从内到外层层触发。
以此为例:
<div class=爷爷>
<div class=爸爸>
<div class=儿子>
文字
</div>
</div>
</div>
- 按照爷爷 => 爸爸 => 儿子的顺序查找监听函数时,即为事件捕获
- 按照儿子 => 爸爸 => 爷爷的顺序查找监听函数时,即为事件冒泡
事件对象
每当事件触发时,浏览器会把当前事件触发的详情信息记录下来,并把它们封装成一个对象,可以传递给事件处理函数。
e.target 和 e.currentTarget 的区别
e.target 用户操作的元素
e.currentTarget 程序员监听的元素
阻止事件冒泡
通过 e.stopPropagation() 可以阻止事件冒泡和事件捕获
注意:事件冒泡不能被阻止
取消默认事件
e.preventDefault()
-
元素有默认事件才可以取消,如果元素本身没有默认事件或者元素不支持取消默认事件(如滚动条),则调用无效。
-
不可取消冒泡和不可取消默认事件的元素可以通过 MDN 英文版搜索 元素 event ,看到 Bubbles (冒泡) 和(Cancelable)是否支持。
DOM 事件委托
事件委托原理:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理一类型的所有事件。简单来说就是找一个代理人做本来自己该做的事,不监听元素 C 自身,而是监听其祖先元素 P,然后判断 e.target 是不是该元素 C(或该元素的子元素) 。
下面引入两个场景:
场景一:你要给100个按钮添加点击事件,咋办?
答:监听这100个按钮的祖先,等冒泡的时候判断target是不是这100个按钮中的一个。
场景二:你要监听目前不存在元素的点击事件,咋办?
答:监听这个元素的祖先,等点击的时候再看看是不是我想要监听的元素。
场景一 代码
//html部分
<div id='div1'>
<button>click 1</button>
<button>click 2</button>
……
<button>click 100</button>
</div>
//js部分
div1.addEventListener('click',(e)=>{
const t=e.target //用一个变量获取当前被点击的元素
if(t.tagName.toLowerCase()==='button') //t的标签名转小写后为button
console.log('button的内容是'+t.textContent)
})
如果要给多个元素绑定事件,可能需要用到循环,且若为同一事件则十分浪费内存空间。对此,事件委托的第一个优点就是:省内存。
场景二 代码
//html部分
<div id='div1'><div>
//js部分
setTimeout(()=>{
const btn=document.createElement('button')
btn.textContent='click1'
div1.appendChild(btn)
},1000) //设置一个1s后的定时器,往div中添加按钮
div1.addEventListener('click',(e)=>{
const t=e.target
if(t.tagName.toLowerCase()==='button'){
console.log('button被点击了')
}
})
如果要给还未出现的元素绑定事件,必须用到事件委托,因为这时运用常规的DOM方法或者JQuery方法是获取不到元素的,因为定时器的队列是放在“稍后”执行的。对此,事件委托的第二个优点就是:可以监听动态元素。
小总结:事件委托两大优点:省内存;可动态。
封装一个事件委托函数
上述的场景中对于特定的场景写特定的代码,这个就叫做写业务。 (这个就是日常的程序猿做的事情)但如果你能把业务代码抽象出来,写一套代码解决该类场景所有类似问题,那你就牛逼了,这个行为叫做造轮子。
//用户点击#div1里的button时,调用fn函数
//具体业务场景
on('click','#div1','button',()=>{
console.log('button被点击了')
})
//轮子,封装一个on函数
function on(eventType, element, selector, fn) { //传进4个参数,为事件类型,元素,选择器,自定义函数
if (!(element instanceof Element)) { //若element不是一个元素
element = document.querySelector(element) //获取元素
}
element.addEventListener(eventType, (e) => {
const t = e.target
if (t.matches(selector)) { //判断一个元素是否满足一个选择器,返回值为bool
fn(e) //调用函数
}else{
console.log('别点了,这个元素没有监听器')
}
})
}
一个疑问:JS支持事件吗?
答:支持,也不支持,DOM事件不属于JS的功能,属于浏览器提供的DOM功能(含冒泡,捕获等),JS只是调用了DOM提供的addEventListener这个API接口而已。
委托事件的局限
- 事件必须冒泡,而有些事件不会冒泡
- 委托可能会增加CPU负载,因为容器级别的处理程序会对容器中任意位置的事件做出反应,而不管我们是否对该事件感兴趣。但通常负载可以忽略不计
那什么样的事件可以用事件委托,什么样的事件不可以用呢
适合用事件委托的事件
click,mousedown,mouseup,keydown,keyup,keypress
值得注意的是,mouseover 和 mouseout 虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合用事件委托的事件
mousemove,每次都要计算它的位置,非常不好把控,还有 focus,blur 之类的,本身就没有冒泡的特性,自然就不能用事件委托了。