DOM事件模型和事件委托

101 阅读5分钟

什么是 DOM 事件

  • DOM 事件是浏览器的一个功能,是浏览器或者用户针对页面做出的某些动作,比如点击,鼠标移动,键盘输入等。
  • DOM 事件是用户与页面进行交互的核心,当事件触发时,可以绑定一个或多个事件处理函数,来完成我们要实现的功能。

DOM 事件模型

DOM 事件模型分为两种,事件冒泡和事件捕获。当某一事件触发时,先执行捕获再执行冒泡。

addEventListener:绑定事件的监听函数

element.addEventListener(eventType, fn, bool)

  • 当不传bool或为falsy值时,函数将会被放在冒泡阶段执行,并返回事件信息
  • 当bool为true时,函数将会被放在捕获阶段执行,并返回事件信息

示意图:

事件模型.png

事件冒泡

子元素的事件类型触发时,所有父元素上绑定同类型事件均会从内到外层层触发。

事件冒泡

子元素的事件类型触发时,所有父元素上绑定同类型事件均会从内到外层层触发。

以此为例:

<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 之类的,本身就没有冒泡的特性,自然就不能用事件委托了。