DOM事件&事件委托

175 阅读4分钟

什么是DOM?

文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。

DOM事件流

  • 事件流描述的是从页面中接收事件的顺序

  • 事件发送时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流

  • 请看下图:

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

我们可以看到分别是

  • 捕获阶段
    • 网景公司最早提出的,由DOM最顶层节点开始,然后逐级向下传播到最具体的元素的过程。
    • 浏览器检查元素的最外层祖先<html>,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。
    • 然后,它移动到<html>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。
    • 即:从外向内找监听函数
  • 冒泡阶段
    • E最早提出的,事件开始由最具体的元素接受,然后逐级向上传播到DOM最顶层节点的过程。
    • 浏览器首先检查被点击元素,(就是文字),然后看是否在冒泡阶段中有Onclick事件,如果是,就运行
    • 然后,寻找下一个parentNode, (就是儿子div),然后看是否在冒泡阶段中有Onclick事件,如果是,就运行
    • 然后再找下一个ParentNode, (就是爸爸div)
    • 即:从内向外找监听函数
  • 当前目标阶段

浏览器调用顺序

文档名为DOMLevel2EventsSpecification,规定浏览器应该同时支持两种调用顺序,首先捕获顺序,然后是冒泡顺序,有监听就调用,并提供时间信息,没有就跳过

  • 那是不是事件会调用两次呢?
  • 提供了api总的参数可以让开发者自己选择是事件放在捕获阶段还是冒泡阶段
  • 使用addEventListener监听事件时,addEventListener('click', fn, bool)
  • 如果第三个参数bool 不传,或者传false, 那么我们会在冒泡阶段调用fn
  • 如果第三个参数Bool传值为true, 那么我们会在捕获阶段调用fn
eventTarget.onclick = funciton(event){
	// 这个 evnet 就是事件对象,我比较喜欢缩写成 e 
}
eventTarget.addEventListener('click',function(event){
	// 这个 evnet 就是事件对象,我比较喜欢缩写成 e 
})

事件对象阻止默认行为

捕获是不可以阻止,取消的, 冒泡可以。

  • e.stopPropagation() 可取消冒泡,浏览器就不再向上走了。
  • e.preventDefault()可以取消默认事件

e.target 和 e.currentTarget 的区别

  • e.target: 返回触发事件的对象。 即用户操作的对象。(假设:你点击了谁就是谁)

  • e.currentTarget : 程序员监听的元素, 即你绑定了谁就是谁

  • this就是e.currentTarget

捕获和冒泡一句话

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

事件委托

什么是事件委托?

  • 不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
  • 由于冒泡阶段,浏览器从用户点击的内容从下往上遍历至 window,逐个触发事件处理函数,因此可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件
  • 这样我们只操作了一次DOM, 省内存、可以监听动态元素

常用场景

场景一

  • 怎么给100个按钮添加点击事件?
  • 找到他们的共同属于那个元素,那就监听这个共有元素,等冒泡的时候判断 target是不是这100个按钮其中的一个 场景一

场景二

  • 怎么监听目前不存在的元素的点击事件呢?
  • 监听共有元素,等到点击的时候看看是不是我想要监听的元素

场景二

封装事件委托

写出一个函数 on('click','#div1','button',fn) 当用户点击#div1的button元素时,调用fn函数。要求用事件委托。

答案一

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

答案二