DOM事件机制和事件委托

256 阅读3分钟

DOM事件机制

DOM事件机制主要有2个阶段,分别是:捕获阶段和冒泡阶段
事件捕获(event capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件
事件冒泡(dubbed bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点
我们在使用addEventListener监听事件时,addEventListener('click', fn, bool) 如果第三个参数bool 不传,或者传false, 那么我们会在冒泡阶段调用fn 如果第三个参数Bool传值为true, 那么我们会在捕获阶段调用fn

2.png

取消冒泡

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

target 和 currentTarget的区别

e.target 用户正在操作的元素
e.currentTarget 程序员在监听的元素
举例:

<div>
  <span>文字</span>
</div>

假设我们监听的是div, 但用户实际点击的是文字,那么
e.target就是span标签
e.currentTarget就是div标签

事件委托

什么是事件委托

在冒泡阶段,浏览器从用户点击的内容从下至上遍历至window,诸葛出发事件处理函数,因此可以监听一个祖先节点来同时处理多个子节点的事件

应用场景

1.如果要给100个按钮添加点击事件
通过事件委托,监听100个按钮的祖先,等冒泡的时候,判断target是不是这100个按钮中的一个
2.如果要监听目前不存在的元素的点击事件
通过事件委托 :监听已有的祖先元素,等冒泡时,判断点击的元素是不是想要监听的元素
事件委托的优点
1.省监听数,省内存
2.便于监听动态元素

代码实现

需求:监听所有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元素

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

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

以上代码有不足之处,如果被点击元素有多个父元素则不一定能正常匹配,实现的思路是通过递归向上找父节点,直到找到用户正在操作的元素,同时还要规定递归的中止条件,寻找的范围不能超过parentElement

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