阅读 139

DOM事件机制和事件委托

引入:

对于以下代码

<div class="爷爷">
		<div class="爸爸">
			<div class="儿子">
				文字
			</div>
		</div>
	</div>复制代码
  • 同时我们爷爷div, 爸爸div, 儿子div 分别添加事件监听fn1, fn2, fn3

提问1:

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

  • 点击文字,算不算点击爸爸?

  • 点击文字,算不算点击爷爷?

答案:都算

提问2:

  • 点击文字,最先调用fn1, fn2, fn3的哪一个?

  • 答案是都行,如果是捕获机制,就先从外到内,先调用fn1

  • 如果是冒泡机制,就从内到外,即先调用fn3

  • 接下来我们详细分析DOM事件机制和事件委托

DOM事件机制

DOM事件机制主要有2个阶段,分别是:捕获阶段和冒泡阶段

1.什么是捕获和冒泡?

  • 当一个事件发生在具有父元素的元素上时,现代浏览器运行两个不同的阶段 - 捕获阶段和冒泡阶段。

在捕获阶段:

  • 浏览器检查元素的最外层祖先<html>,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。

  • 然后,它移动到<html>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。

在冒泡阶段:

  • 浏览器首先检查被点击元素,(在开头的例子中,就是文字),然后看是否在冒泡阶段中有Onclick事件,如果是,就运行

  • 然后,寻找下一个parentNode, (在开头的例子中,就是儿子div),然后看是否在冒泡阶段中有Onclick事件,如果是,就运行

  • 然后再找下一个ParentNode, (在开头的例子中,就是爸爸div)

1.png

  • 我们在使用addEventListener监听事件时,addEventListener('click', fn, bool)

  • 如果第三个参数bool 不传,或者传false, 那么我们会在冒泡阶段调用fn

  • 如果第三个参数Bool传值为true, 那么我们会在捕获阶段调用fn

2.png

2.取消冒泡

  • 捕获不可以取消,但是冒泡可以取消,e.propagation()就可

  • 但是有一些事件不可以取消冒泡,比如scroll事件,具体可以在MDN上查询

3.target 和 currentTarget的区别

  • e.target 用户正在操作的元素

  • e.currentTarget 程序员在监听的元素

举例:

<div>
	<span>文字</span>
</div>复制代码
  • 假设我们监听的是div, 但用户实际点击的是文字,那么

e.target就是span标签

e.currentTarget就是div标签

4.总结捕获和冒泡:

  • 捕获:当用户点击按钮,浏览器会从 window 从上向下遍历至用户点击的按钮,逐个触发事件处理函数。

  • 冒泡:浏览器从用户点击的按钮从下往上遍历至 window,逐个触发事件处理函数。

事件委托

1.什么是事件委托

  • 由于冒泡阶段,浏览器从用户点击的内容从下往上遍历至 window,逐个触发事件处理函数,

  • 因此可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件

2.常见应用场景

  • 场景一:

我们要给100个按钮添加点击事件,怎么办?

最笨的办法:直接给100个按钮都addEventListener

有了事件委托后:监听这100个按钮的爸爸,等冒泡的时候,判断target是不是这100个按钮中的一个

  • 场景二:

我们要监听目前不存在的元素的点击事件,咋办?

有了事件委托:监听祖先,等到冒泡时,判断点击的元素是不是我想要监听的元素

所以使用事件委托的好处就是

1.可以省掉监听数,从而节省内存

2.可以监听动态元素

3.代码实现

  • 需求:监听所有的li标签,如果用户点击li标签,就console.log('用户点击了Li标签')

<ul id="test">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>复制代码
  • 那么实现的JS代码就是:

// 监听父元素 ul#test
test.addEventListener('click', (e)=> {
  //通过浏览器传进来的e参数,找到当前点击元素
  const t = e.target 
  // 判断当前元素是不是Li标签
  if(t.matches('li') {
    console.log('用户点击了li')
  }
})复制代码
  • 实现思路很简单

1.首先监听父元素

2.然后根据浏览器传进去的事件信息,拿到当前点击元素

3.再判断当前点击元素是不是li元素, 如果是,就console.log('用户点击Li标签')

基于此,我们可以封装一个事件委托函数

on('click', '#test', 'li', ()=>{
    console.log('用户点击了li')
})

function on(eventType, parentElement, selector, fn) {
    // 先判断是不是element, 
   //如果传进来的是选择器,不是element本身,就先变成element,
    // 因为只有element才能监听事件
    if (!(parentElement instanceof Element)) {
        parentElement = parentElement.querySelectorAll(parentElement)
    }
    parentElement.addEventListener(eventType, (e)=>{
        let target = e.target
        if (target.matches(selector)) {
            fn(e)
        }
    })
}复制代码
  • 但是以上这种实现有一个小问题,那就是如果被点击元素有多个父元素怎么办?

<ul id="test">
	<li>
		<p>
      <span>1</span>
    </p>
	</li>
	<li>
		<p>
      <span>2</span>
    </p>
	</li>
	<li>
		<p>
      <span>3</span>
    </p>
	</li>
	<li>
		<p>
      <span>4</span>
    </p>
	</li>
</ul>复制代码
  • 我们需要做的就是:

  • 递归地向上多找几层父节点,直到找到li标签,

  • 同时还必须限定,寻找的范围不能超过parentElement,

  • 拿上面的例子来说,不可以越过ul标签,去找body标签

on('click', '#test', 'li', ()=>{
    console.log('用户点击了li')
})

function on(eventType, element, selector, fn) {
    if (!(element instanceof Element)) {
        element = document.querySelectorAll(element)
    }
 
    element.addEventListener(eventType, (e)=>{
        let target = e.target
        // 如果匹配到了selector就跳出循环
        while(!target.matches(selector)){
            if (target === element){
                //已经找到了父元素,说明还没找到,就设置为null
                target = null
                break
            }
            target = target.parentNode
        }
      
      	// 找到了target, 就调用函数
        target && fn.call(target, e)
        
    })
}复制代码

总结

1.总结捕获和冒泡:

捕获:当用户点击按钮,浏览器会从 window从上向下遍历至用户点击的按钮,逐个触发事件处理函数。

冒泡:浏览器从用户点击的按钮从下往上遍历至 window,逐个触发事件处理函数。

3.png

2.什么是事件委托

定义:监听祖先元素,从而监听一个,同时操作多个后代

  • 由于冒泡阶段,浏览器从用户点击的内容从下往上遍历至 window,逐个触发事件处理函数,

  • 因此可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件


作者:kk前端冲冲冲
链接:https://juejin.cn/post/7022689539483238407


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐