阅读 154

DOM 事件模型或 DOM 事件机制

事件

通俗理解,事件是用户或者浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)按钮等,这里的click就是事件的名称。JS与html之间的交互是通过事件实现的。

每个事件都有事件监听器(有时也叫事件监听器),也就是触发事件时运行的代码块。严格来说事件监听器监听事件是否发生,然后事件处理器对事件做出反应。

事件模型

一个事件发生后,会在子元素及父元素之间进行传播(propagation),这种传播分为三个阶段。

DOM事件传播的三个阶段:

  • 捕获阶段

  • 目标阶段

  • 冒泡阶段

事件捕获和事件冒泡

  • 事件捕获:由外向内找监听函数。

  • 事件冒泡:由内向外找监听函数。

例子:

<div class="grandfather">   <div class="father">     <div class="son"></div>     hi   </div> </div> //假如给三个div分别添加事件监听 fnYe / fnBa / fnEr 复制代码

问题一:

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

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

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

答案:都算

问题二: 调用监听函数顺序是什么呢?

事件捕获:fnYe > fnBa > fnEr , 也就是从外到内去调用

事件冒泡:fnEr > fnBa > fnYe , 也就是从内到外去调用

W3C在2002年发布了标准, 文件名为DOM Level 2 Events Specification

规定浏览器同时支持两种调用顺序

首先按爷爷->爸爸->儿子顺序看有没有函数监听 , 先事件捕获

然后按儿子->爸爸->爷爷顺序看有没有函数监听 , 再事件冒泡

如图所示:

1.png

如何指定走捕获还是冒泡呢?

baba.attachEvent('onclick',fn)//冒泡 baba.addEventListener('click',fn)//捕获 baba.addEventListener('click',fn,bool)//w3c制定 复制代码

  • 如果bool不传或为falsy

  • 就让fn走冒泡,即当浏览器在冒泡阶段发现 babafn 监听函数,就会调用fn,并提供事件信息。

  • 如果bool为true

  • 就让fn走捕获,即当浏览器在捕获阶段发现 babafn 监听函数,就会调用fn,并且提供事件信息。

代码示例:

HTML:

<div class="level1 x">   <div class="level2 x">     <div class="level3 x">       <div class="level4 x">         <div class="level5 x">           <div class="level6 x">             <div class="level7 x">             </div>           </div>         </div>       </div>     </div>   </div> </div> 复制代码

CSS:

* {   box-sizing: border-box; } div[class^=level] {   border: 1px solid;   border-radius: 50%;   display: inline-flex; } .level1 {   padding: 10px;   background: purple; } .level2 {   padding: 10px;   background: blue; } .level3 {   padding: 10px;   background: cyan; } .level4 {   padding: 10px;   background: green; } .level5 {   padding: 10px;   background: yellow; } .level6 {   padding: 10px;   background: orange; } .level7 {   width: 50px;   height: 50px;   border: 1px solid;   background: red;   border-radius: 50%; } .x{   background: transparent; } 复制代码

JS:

const level1 = document.querySelector('.level1') const level2 = document.querySelector('.level2') const level3 = document.querySelector('.level3') const level4 = document.querySelector('.level4') const level5 = document.querySelector('.level5') const level6 = document.querySelector('.level6') const level7 = document.querySelector('.level7') let n = 1  const fm = (e)=>{   const t = e.currentTarget   setTimeout(()=>{       t.classList.remove('x')   },n*1000)   n+=1 }  const fa = (e)=>{    const t =e.currentTarget    setTimeout(()=>{      t.classList.add('x')    },n*1000)    n+=1  } level1.addEventListener('click',fm,true) //true 走捕获 level1.addEventListener('click',fa) //默认为 false 走冒泡 // 先捕获后冒泡 level2.addEventListener('click',fm,true) level2.addEventListener('click',fa) level3.addEventListener('click',fm,true) level3.addEventListener('click',fa) level4.addEventListener('click',fm,true) level4.addEventListener('click',fa) level5.addEventListener('click',fm,true) level5.addEventListener('click',fa) level6.addEventListener('click',fm,true) level6.addEventListener('click',fa) level7.addEventListener('click',fm,true) level7.addEventListener('click',fa) 复制代码

一个特例

//只有一个 div 被监听(不需要考虑父子关系) div.addEventListenter('click',f1) //冒泡 div.addEventListenter('click',f2,true) //捕获 复制代码

f1 先执行还是 f2 先执行呢?

先捕获在冒泡, f2 先执行?

正确答案:f1 先执行。

当没有父子关系时,谁先监听谁就先执行。

target 与 currentTarget的区别

  • e.target 用户操作的元素

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

例子:

<div>   <span>文字</span> </div> 复制代码

  • e.target 就是 span ,用户操作的元素。

  • e.currentTarget 就是 div , 程序员监听的元素。

取消冒泡

e.stopPropagation() 可打断冒泡,浏览器不再向上走

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

注意:捕获不可以取消但是冒泡可以(有些事件也不能够取消冒泡,例如: scroll 滚动条)

事件委托

事件委托的好处:

  1. 省内存(省监听数)

  2. 可以监听动态元素

例子一:

//有 100 个 button <div id="div1">   <span>span1</span>    <button>click 1</button>    <button>click 2</button>    <button>click 3</button>    <button>click 4</button>    <button>click 5</button>                  .                  .                  .    <button>click 100</button> </div> 复制代码

如果有100个 button 怎么办呢,不可能创建 100 个监听器吧。

将监听委托给 div

我们只需要监听这个100个按钮的祖先,等冒泡的时候判断 e.target 是不是这100个按钮中的一个,节省内存。

div1.addEventListener('click',(e)=>{   const t = e.target   if(t.tagName.toLowerCase()==='button'){     console.log('button 被点击了') } }) 复制代码

例子二:

button 延迟1秒后才出现,我们如何监听呢?

// 延迟1秒后,创建 button setTimeout(()=>{   const button = document.createElement('button')   button.textContent='click'   div1.appendChild(button) },1000) div1.addEventListener('click',(e)=>{   const t = e.target   if(t.tagName.toLowerCase()==='button'){     console.log('button被点击')   } }) 复制代码

监听祖先,等点击的时候看看是不是监听的元素即可,监听动态元素。

封装事件委托

setTimeout(()=>{   const button = document.createElement('button')   button.textContent='click'   div1.appendChild(button) },1000) on('click','#div1','button',()=>{  //'#div'是选择器不是元素   console.log('button 被点击啦') }) // 声明 on 函数 function on(eventType,element,selector,fn){ //判断 element 类型是不是元素   if(!(element instanceof Element)){        // 不是则找到指定的元素并赋值给 element        element = document.querySelector(element)      }   element.addEventListener(eventType,(e)=>{   const t= e.target   if(t.matches(selector)){      fn(e)    } }) }


作者:Biao
链接:https://juejin.cn/post/7034081742373781511


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