DOM 事件模型和事件委托

1,707 阅读5分钟

事件是用户或者浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)、指针悬浮(mouseover)、提交(submit)等,这是JavaScript 与 HTML 交互的基础,要实现用户与页面的交互,需要先对目标元素绑定特定的事件以及设置事件处理函数,然后用户触发事件执行事件处理函数,最后产生交互效果。DOM是一种树形结构,那么当触发某个元素的时候其父辈元素也被认为触发了吗?例如当点击某个元素的时候,其父辈元素也被认为点击了吗?答案是当触发某个元素的时候其父辈元素也会同时触发,这样就会产生一个执行顺序的问题,是自上而下(从父辈元素开始)还是自下而上(从当前元素开始)执行呢?

1. DOM监听事件

DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequestAudioNodeAudioContext)也部署了这个接口。

该接口主要提供三个实例方法。

  • addEventListener():绑定事件的监听函数
  • removeEventListener():移除事件的监听函数
  • dispatchEvent():触发事件

一个事件发生后,会在子元素及父元素之间进行传播(propagation),这种传播分为三个阶段。

(这种三阶段的传播模型,使得同一个事件会在多个节点上触发。)

  1. 由外向内找监听函数就是事件捕获
  2. 在目标节点触发事件
  3. 由内而外找监听函数就是事件冒泡

通俗一点来说就是一个事件被触发时,浏览器会自动从用户操作标签外的最上级标签逐渐向里检查是否有相同事件,如果有则触发,如果没有则继续向下检查直到用户操作的标签,这过程称为捕获,此时浏览器会继续由用户操作标签继续向上级标签检查,如果有相同事件则触发,如果没有则继续向上检查直到最上级元素为止,此过程称为冒泡。(有监听函数就执行,并提供事件信息,没有就跳过)

事件传播的最上层对象是window,上例的事件传播顺序,在捕获阶段依次为window、document、html、body、父节点、目标节点,在冒泡阶段依次为目标节点、父节点、body、html、document、window。

DOM事件传播的三个阶段:捕获阶段,目标阶段,冒泡阶段

image.png

2. 事件绑定API

IE5:    div1.attachEvent('onclick',fn)//冒泡

网景:   div1.addEventListener('click',fn)//捕获

W3C:    div1.addEventListener('click',fn,bool)
复制代码

如果bool不传或为falsy

就让fn走冒泡,即当浏览器在冒泡阶段发现baba有fn监听函数,就会调用fn,并提供时间信息。

如果bool为true

就让fn走捕获,即当浏览器在捕获阶段发现baba有fn监听函数,就会调用fn,并且提供事件信息。

image.png

选择哪一个事件流可以由开发者自己决定

3. target 与 currentTarget的区别

区别:

e.target 用户操作的元素

e.currentTarget 是程序员监听的元素

this是e.currentTarget但是在监听代码中个人不推荐使用this

举例:

div>span{文字} 用户点击文字

e.target就是span

e.currentTarget就是div

一个特例

背景:

只有一个div被监听(不考虑父子同时被监听)

fn分别在捕获阶段和冒泡阶段监听click事件

用户点击的元素就是开发者监听的

代码:

div.addEventListenter('click',f1)   //冒泡

div.addEventListenter('click',f2,true)   //捕获

请问,f1先执行还是f2先执行? //先f1 后执行f2

如果把两个调换位置? //先执行f2 后执行f1

总结:谁先监听谁先执行。

level7.addEventListener('click',()=>{
      console.log(2)
},true)//捕获
level7.addEventListener('click',()=>{
      console.log(1)
})//冒泡

4. 取消冒泡 e.stopPropagation()

  • 捕获是不可取消的,但是冒泡可以取消。
  • 所有冒泡皆可取消,默认动作有的可以取消有的不能取消。
  • e.stopPropagation() 可以中断冒泡,浏览器不再向上走了。

5. 不可阻止默认动作

有些事件不能阻止默认动作

  • MDN搜索scroll event,可以一个表格看到,表格中有Bubbles和Cancelable
  • Bubbles的意思是该事件是否冒泡
  • Cancelable的意思是开发者是否可以阻止默认事件
  • Cancelable与冒泡无关

6. 事件委托

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

var ul = document.querySelector('ul');

ul.addEventListener('click', function (event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    // some code
  }
});

上面代码中,click事件的监听函数定义在<ul>节点,但是实际上,它处理的是子节点<li>click事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而不用在每个<li>节点上定义监听函数。而且以后再添加子节点,监听函数依然有效。

总结一下事件代理的优点:

  • 省监听数(内存)
  • 可以监听动态元素

注意的是:本章节讲的是DOM的事件,JS只是调用了DOM提供的addEventListener方法,其实JS不支持事件,除非开发者手写一个事件系统。