DOM 事件与事件委托

376 阅读3分钟

本篇博客是作为个人自学记录,如有不足之处,请批评指正。

前言

代码:

<div class = 爷爷>
	<div class = 爸爸>
		<div class = 儿子>
			文字
		</div>
	</div>
</div>

<!-- 分别为三个 div 添加事件监听 fnYe/ fnBa/ dnEr -->

请问1:点击了谁?

  • 点击文字,算不算点击儿子?
  • 点击文字,算不算点击爸爸?
  • 点击文字,算不算点击爷爷?

请问2:调用顺序

  • 点击文字,最先调用 fnYe/ fnBa/ dnEr 中哪一个函数?
  • 答案:都行
  • IE5 认为先调用fnEr,网景认为先调用 fnYe
  • 最后 W3C 发布了标准
W3C 规定浏览器应该同时支持两种调用顺序:
首先:按 爷爷 → 爸爸 → 儿子 顺序看有没有函数监听(从外向内)
然后:按 儿子 → 爸爸 → 爷爷 顺序看有没有函数监听(从内向外)
有监听函数就调用,并提供事件信息,没有就调过

事件捕获和事件冒泡

  • 从外向内 找监听函数,叫 事件捕获
  • 从内向外 找监听函数,叫 事件冒泡
  • 先捕获,后冒泡

W3C 事件模型

addEventListener

时间绑定 API

// IE 5:
.attachEvent('onclick', fn) //冒泡

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

// W3C:
.addEventListener('click', fn, bool)
// 如果 bool 不传或为 falsy 就走冒泡
// 如果 bool 为 true 就走捕获

小结一

疑问:

  • 儿子被点击了,算不算点击爸爸? 答案:算
  • 先调用爸爸的函数还是先调用儿子的函数? 答案:根据W3C事件模型,先捕获再冒泡,先从爸爸到儿子,再从儿子到爸爸

捕获与冒泡:

  • 捕获:先调用爸爸的监听函数
  • 冒泡:先调用儿子的监听函数

W3C 事件模型

  • 先捕获,再冒泡?(但是可以阻止冒泡)
  • 注意事件被传给所有监听函数
  • 事件结束后,对象就不存在了

target 和 currentTarget

区别

  • target 是用户操作的元素
  • currentTarget 是程序员监听的元素
  • this 是 currentTarget,不推荐使用它

举例

<div>
  <span>
    文字
  </span>
</div>
  • 用户点击文字
  • target 就是 span
  • currentTarget 就是 div

特例

  • 当只有一个 div 被监听(不考虑父子同时被监听)
  • fn 分别同时在捕获阶段和冒泡阶段监听 click 事件
  • 谁先监听谁先执行

阻止冒泡

捕获是不能取消的,但是冒泡可以

e.stopPropagation

(e)=>{
  e.stopPropagation()
}

一般用于封装某些独立的组件

不可取消冒泡

scroll 事件不可取消冒泡

点击查看文档

小结二

target 和 currentTarget

  • 一个是用户点击的,一个是开发者监听的

阻止冒泡

  • e.stopPropagation()

事件的特性

  • Bubbles 表示是否冒泡
  • Cancelable 表示是否支持开发者取消冒泡
  • 如:scroll 不支持取消冒泡

如何禁用滚动

  • 取消特定元素的 wheel 和 touchstart 的默认动作

事件委托

场景一:

  • 你要给100个按钮添加点击事件,咋办?
  • 答案:监听这100个按钮的祖先,等冒泡的时候判断 target 是不是这100个按钮中的一个

场景二:

  • 你要监听目前不存在的元素的点击事件,咋办?
  • 答案:监听祖先,等点击的时候看看是不是我想要监听的元素即可

事件委托其实很好理解

  • 利用事件冒泡的原理,把事件监听放在祖先元素(如父元素、爷爷元素)上。

优点

  • 节约监听数量
  • 可以监听动态生成的元素

封装事件委托

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)
    }
  })
}

// 给一个元素添加监听,然后看当前的 target 是不是满足这个 selector,如果满足就调用函数,不满足的话就不调用