DOM事件与事件委托

379 阅读6分钟

DOM事件

  • 由此案例引入DOM事件
  <div class="爷爷">
        <div class="爸爸">
            <div class="儿子"></div>
            文字
        </div>
    </div>

  • 给三个div分别添加事件监听fnYe/fnBa/fnEr
  • 结论:点击文字,算点击儿子、爸爸、爷爷,都算
  • 点击文字,最先调用哪一个函数? 都行
  • IE5先调用儿子,网景(firefox)先调用爷爷
  • 于是w3c发布标准

W3C标准(执行机制)

  • 文档名为:DOM Level2 Events Specification
  • 规定浏览器应该同时支持两种顺序
  • 首先按由外向内顺序
  • 然后按由内向外
  • 有监听函数调用,并提供事件信息,没有就跳过

事件捕获、事件冒泡

  • 从外到内找监听函数,叫事件捕获
  • 从内向外找监听函数,叫事件冒泡 IE浏览器的

疑问:这样是否要执行两次?

不是,开发者要自己选择把函数放到捕获阶段还是冒泡阶段,取决于api传参

示意图(如下)

1633932346(1).png

  • 蓝色填充被点击
  • 走两个过程,从window到属于捕获阶段,从到window属于冒泡阶段

addEventListener

  • 事件绑定api
    • IE5*:baba.attachEvent('onclick',fn) 冒泡
    • 网景:baba.addEventListener('click',fn) 捕获
    • W3C: `baba.addEventListener('click',fn,bool)
  • 如果bool不传参数或者为falsy值
    • 让fn走冒泡,当发现监听函数,就调用fn
  • 如果bool为true
    • 让fn走捕获,当发现监听函数,调用fn
  • 小结:W3C其实是默认支持冒泡的
  • 注意默认状态下两个路线是都会走的,顺序是固定的,只是开发者选择函数在哪条路执行,另一个路线空跑
  • 事件捕获、冒泡形象展示github.com/hxa0/DOM-ev…

示意图

1633933393(1).png

小补充

target和currentTarget

  • 区别:
    • e.target是用户操作的元素、
    • e.currrentTarget是监听的元素
    • this是e.currentTarget
  • 举例
    • div>span{文字},用户点击文字
    • e.target就是span
    • e.currentTarget就是div

特殊情况

  • 只有一个div被监听,不用考虑父子元素同时被监听时,fn分别在捕获和冒泡阶段监听事件,用户点击的元素就是开发者所监听的

举例

  • div.addEventListener('click',f1)
  • div.addEventListener('click',f2,true)
  • 当不考虑父子元素,哪个在前,先执行哪个

取消冒泡

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

  • e.stopPrpagation()可中断冒泡,浏览器不在向上走
  • 一般用于封装某些独立的组件

1634025498(1).png

不可阻止默认动作

有些事件不能阻止默认动作

  • MDN搜索event,例如scrollevent,clickevent,英文文档有详细的介绍

  • 例:搜索 scrollevent

1634043667(1).png

阻止滚动

scroll事件不可阻止默认动作

  • 阻止scroll默认动作不起作用,因为先有滚动才有滚动事件
  • 要阻止滚动,可阻止wheel和touchstart的默认动作
  • 注意要找准滚动条所在的元素
  • 但是滚动条还能用,可用css将滚动条宽度设置为0

css设置也可以

  • 使用overflow:hidden,可以直接取消滚动条
  • 但此时JS依然可以修改scrollTop

代码实现

ca5c81cc2835d2ac70cee73d4add652.png

  • 去掉滚动条 191a5bd97d90d6d9d55ea42a2e6058c.png
  • 阻止滚轮事件
  • 阻止触屏事件(手机端)

自定义事件

浏览器自带事件

案例

1634045337(1).png

1634045394(1).png

6d3ce404d1781713ea324d07f761f70.png

事件委托

场景一

  • 要给100个按钮添加事件
  • 方案:监听这100个按钮的父级元素,等冒泡的时候判断target是不是这100个按钮中的一个

代码

5e83c32796611a0cdc91c2c1669f46d.png

  • div1为这100个按钮的父级元素,添加鼠标点击事件监听
  • 声明变量t,t为用户操作的元素
  • 如果t的标签名是button(转化成小写)
  • 打印被点击的元素id

场景二

  • 要监听目前不存在的元素的点击事件
  • 方案:监听其父元素,等点击的时候判断是不是要监听的

代码

43f5f38627adc814474f80a4691aece.png

  • div1里面不是立即出现button
  • div1添加点击事件监听
  • 声明变量t,t为操作的元素(点击的元素)
  • 判断条件:如果操作的元素标签名字为button
  • 打印button被点击,表明点击事件

封装事件委托

效果

  • 写出一个函数on('click','#testDiv','li',fn)
  • 当用户点击#tesDiv里的li元素时,调用fn函数
  • 使用到事件委托

代码实现

1634107826(1).png

  • 定义on函数,接收参数:事件类型,元素,选择器(要匹配的元素),函数fn(调用的函数)
  • 先判断,第二参数,如果不是元素,找这个元素
  • 第二参数:元素,添加事件监听(机制参考前面内容)
  • 判断如果被操作的元素匹配提供的选择器,那么就执行fn,并且把事件传参给fn

小补充

  • 以上这个代码是有局限性的,仅适用于#div1>button(click)这种情况

场景变化

1634108000(1).png

  • 如果结构是#div!>button>span(click)
  • 从下方代码的判断条件if(t.matches(selector)),操作元素t是span标签,选择器是button标签,两个不匹配
  • 选择器与实际操作的元素存在父子级关系的落差(不仅限于父子带,有可能相隔若干代)

方案

  • 利用事件监听的最初逻辑,当点击某个元素,它的父级元素以及以上父级元素都算做被点击
  • 递归判断target/target父元素/父元素的父元素/.....

代码

1634108983(1).png

  • 看被操作的元素是否是buttonbutton(line21)
  • 如果不符合,就让这个元素等于其父元素(line26)
  • 如果元素到了#div1这个级别,那么就要停止(不能超出div1这一级别),如果找不到(null),就停止了,不要找了(line 22-24)
  • 最后判断如果找到的上级元素是匹配选择器的,调用fn,第一个参数传e,第二个参数传找到的元素(el),最开头传this(el) (line28)
  • 以上代码可以整合进jQuery,调用:$('#xxx').on('click''li',fn)(举例)

JS支持事件吗?

  • DOM事件不属于JS的功能,属于浏览器的功能
  • DOM事件与JS属于平行并列的功能
  • JS只是调用了DOM提供的addEventListener功能而已

总结

DOM事件机制

  • 事件捕获:当用户触发事件,浏览器会从window,由外向内(逐级从父代向子代),遍历到用户操作的元素,逐个触发处理事件的函数
  • 事件冒泡:浏览器从用户操作的元素,由内向外(逐级从操作的元素向父代),遍历到window,逐个触发处理事件的函数
  • 这个两个顺序是不会改变的,捕获过程不可中断,冒泡过程可以控制中断
  • 遍历到的函数不是调用两次,开发者可以选择把函数放到任意一个阶段

事件委托机制

  • 原理:在冒泡阶段,浏览器会从被操作元素逐级向上遍历,触发事件处理函数。
  • 实现方法:当用户操作某个元素,不监听被操作元素本身,而是监听被操作元素的某个父级(或者以上)元素,触发事件处理函数。这样就能实现监听一个节点处理多个节点的效果
  • 优点:节省内存,可以监听动态元素