DOM事件与事件委托

474 阅读9分钟

什么叫EventListner?

EventListener 即事件监听,意思是当你写了.onclick / .addEventListener 这类代码之后,我们可以从控制台中的Event Listener页面中看到事件类型-dom节点-处理函数的信息

  • 之所以叫事件监听,是因为当你写了代码以后浏览器就在等待你去触发目标元素,这不就是监听着吗? 然后当你触发了以后,就执行相应的函数。

基本概念

  • 为什么叫DOM事件而不是JS事件呢?
  • DOM事件是浏览器的功能而不是JS自己就有的,JS仅仅是调用DOM对象.addEventListener
  • 如何JS手写一个事件系统呢?其实是用队列实现的,暂时还不需要了解这个,可能是考点

所有事件速查

mdn---所有事件速查

事件流(捕获阶段->冒泡阶段)

  • 点击子元素其实就是点击父元素
  • 但是先触发子还是先触发父 身上的函数是不确定的
  • w3c规定了先捕获阶段 后冒泡阶段 也就是先外往里 再从里往外

事件流(捕获阶段->冒泡阶段)的完整过程

  • e.currentTarget 瞬间消亡,因此setTimeout之前必须留一份e.currentTarge
  • addEventListener 正确使用是 不写第三个参数或者falsy 都是冒泡过程,true值则为捕捉
  • 最里层是最特殊的,由于它是立马进行两个函数(捕捉函数->冒泡函数),写代码时要千万留意捕捉函数与冒泡函数的代码书写顺序会影响函数执行顺序
    • 如果对最里层先addEventListener冒泡函数后addEventListener捕捉函数,则即使理应轮到它捕捉时,它也先冒泡 完整示例看这里

冒泡的本质

  • 拿click事件举例
  • 即使里层元素未绑定click事件函数
  • 只要外层元素绑定了click事件函数
  • 一旦里层元素click,即可触发外层元素的click函数
  • 在线示例
  • 理解冒泡才能理解什么叫事件委托

addEventListener

  • 默认是把函数挂载在冒泡阶段
node.addEventListener('click',fn,bool) // 不写bool或者falsy,即冒泡

addEventListener VS dom.onclick

document.body.onclick=function(){...}
document.body.addEventListener('click',function(){...})
  • addEventListener支持对同一个eventTarget绑定多个处理函数,而前者不支持

事件参数e

e转瞬即逝

e.target vs e.currentTarget

e.target -> 用户用鼠标点击的那个元素 e.currentTarget -> 正在捕获或者冒泡过程中触发的当前元素

事件函数中的this

this-> e.currentTarget 但是非常不建议使用 因为根本记不住

e.stopPropagation() 取消事件的执行

浏览器的默认行为

浏览器的默认行为的理解就是,对特定的元素触发了特定的事件后,浏览器会为我们做的事情

  • 对某个元素执行了某个事件函数以后的结果是有可能可以控制的,比如以下:
- 对a标签执行click事件 如果设置了href 则浏览器的默认行为 执行跳转
- 对input执行click事件 浏览器的默认行为是 获取焦点
- 对某元素执行submit事件 浏览器的默认行为是 提交其所在表单
- 对checkbox执行click事件 浏览器的默认行为是 会选中或者反选

e.preventDefault() 阻止浏览器的默认行为

  • preventDefault仅仅用于阻止系统默认行为,无法取消事件
    • 阻止系统默认行为是不分事件流阶段的
    • 先用 e.cancelable 来判断该事件是否支持取消或者查表。如果不支持,则preventDefault()将没有效果。

scroll与wheel事件的区别

  • scroll事件是在滚动条滚动时触发,滚动条必须存在才会会触发,只要滚动条滚动,就会触发。
    • 触发方式:鼠标滚轮,鼠标拖动,键盘上下键,或者设置的滚动函数,如 scrollTo,scrollBy,scrollByLines, scrollByPages
  • wheel 事件在鼠标滚轮在元素上下滚动时触发。(顺便一提,手机端是touchstart)
  • 当鼠标滚轮滚动时,onwheel事件先被触发,若滚动条滚动,则onscroll事件会相继被触发。
  • 简言之,wheel事件函数的默认行为是页面下滚;scroll事件函数的默认行为是没有的,scroll时的页面下滚是自然反应

为什么scroll事件中不可阻止浏览器默认行为?

  • 很简单,因为拖动滚动条的时候,页面下滚是本该有的反应而不是浏览器为我们做的事情
  • 而wheel事件本身指的是鼠标滚轮的滚动,因此浏览器为我们实现了让页面下滚的动作而已

问题:若要阻止用户在当前页面下滑,该如何操作?

  • 思路1:对window挂载wheel事件,函数中e.preventDefault()
    • 在线示例
    • 缺点是仅仅是不让用滚轮下滑,用户依旧可以拖动右边的滚动条实现下滚
  • 思路2:css方式隐藏scroller 压根就不让用户下滚
    • 这个是最彻底的,因为wheel事件e.preventDefault() 的也是这个
::-webkit-scrollbar{
  width:0!important
}
  • 错误的思路:对body之类的元素挂载scroll事件,函数中e.preventDefault(),
    • 因为scroll事件中不可阻止浏览器默认行为

就算scrollbar被隐藏了,js依旧可以scrollTop的方式去操纵scroll

自定义事件

new CustomEvent + dispatchEvent 自定义事件详解示例

事件委托

理解:

  • 事件委托就是把本该绑定在目标元素身上的事件却绑定给它的父级元素身上,虽然不再绑定于目标元素自身,但是利用事件流的冒泡机制特性,用户依旧能够通过目标元素去触发该事件。 要点:
  • 需要判断用户所点击的dom(e.target)是不是匹配得上selector
  • 与冒泡有着本质的区别

原理:

  • 利用事件流的冒泡机制

好处:

  • 1.即使目标元素尚未被创建,由于已经提前绑定给父级,在目标元素创建后,即可触发该事件
  • 2.节省内存资源与代码量:当一个父级元素内存在大量相同的需要监听的元素,通过事件委托的形式我们便不再需要逐一对目标元素进行事件监听 适用情况:
  • 情况1:给100个点击按钮添加点击事件
  • 情况2: 如何监听一个还未创建的元素?
  • 点击查看在线示例

事件委托与冒泡的关系

  • 事件委托是利用冒泡,可以理解为更严格的冒泡
  • 因为冒泡是如果最外面的父有一众子元素,且父挂载了click事件函数,那么只要任意一个子元素click了,那么父的click事件函数就会执行
  • 但是事件委托是限制了只有满足选择器条件的这些子元素,才可以去利用冒泡触发父的函数,并不是我点哪个子元素都可以的

事件委托 VS 原生js的addEventListener

  • 事件委托是需要指定事件类型,父元素,需要查找的子元素(其实是选择器),以及事件函数这四样东西
  • addEventListener是原生JS提供的事件绑定函数,仅仅能为父元素绑定事件,相当于只提供了一个外壳,但缺少了核心代码即判断当前点击的元素是不是正确的元素

简单地封装一个事件委托函数(不会层层往上查找,是个老实人,你selector写啥就判断啥)

更好地封装一个事件委托函数(会层层往上查找)

  • 在理解了上面之后
  • 如下结构,本来是把button的click事件函数挂在body上构成事件委托且只需要判断e.target是不是id为btn1的这个button
  • 但假设这时候JS动态在button中又插了span且内容123
  • 那么如果只简单判断e.target与selector之间是否匹配就不对了,
  • 因为用户点击时点的是span,而你判断逻辑没有变,静态判断的是button,那便不会触发body身上的click事件函数
  • 应该递归实现层层往e.target即用户所点元素的上级去动态查找是不是可以找到匹配selector的元素
  • 这是一个优秀的事件委托函数
<body>
  <button id="btn1">
      <span>123</span>
  </button>
  <button id="btn2">
  </button>
  <button id="btn3">
  </button>
</body>

如何理解好的事件委托函数需要层层往上找?

  • 上面的例子告诉我们,好的事件委托函数需要由e.target(用户所点击的元素)开始一直一级一级往外匹配selector试图找到element
  • 这似乎很难理解,但这就像是花园里有5棵树,事件委托的本意是指定了最高的那一棵才能触发花园的函数,仅仅是为了限制那颗树木而已
  • 本意并不想太细节到这个树木的位置
  • 因此好的事件委托函数需要层层往上找

其余相关demo