简述 DOM 事件模型或 DOM 事件机

DOM 事件模型

DOM 的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequest、AudioNode、AudioContext)也部署了这个接口。

该接口主要提供三个实例方法:

  • addEventListener:绑定事件的监听函数
  • removeEventListener:移除事件的监听函数
  • dispatchEvent:触发事件

事件模型

一个事件发生后,会在子元素及父元素之间进行传播(propagation),这种传播分为三个阶段。
(这种三阶段的传播模型,使得同一个事件会在多个节点上触发。)

  1. 由外向内找监听函数就是事件捕获
  2. 在目标节点触发事件
  3. 由内而外找监听函数就是事件冒泡

通俗一点来说就是一个事件被触发时,浏览器会自动从用户操作标签外的最上级标签逐渐向里检查是否有相同事件,如果有则触发,如果没有则继续向下检查知道用户操作的标签,这过程称为捕获,此时浏览器会继续由用户操作标签继续向是上级标签检查,如果有相同事件则触发,如果没有则继续向上检查直到最上级元素为止,此过程称为冒泡。(有监听函数就执行,并提供事件信息,没有就跳过)

事件传播的最上层对象是window,上例的事件传播顺序,在捕获阶段依次为windowdocument、htmlbody父节点目标节点,在冒泡阶段依次为目标节点父节点bodyhtmldocumentwindow

DOM事件传播的三个阶段:捕获阶段目标阶段冒泡阶段

点击事件

代码:

<div class="grandfather">
  <div class="father">
    <div class="son"></div>
    word
  </div>
</div>

即.grandfather>.father>.son

给三个div分别添加事件的监听fnYe/fnBa/fnEr

提问1:点击了谁?

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

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

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

答案:都算

提问2:调用循序

点击文字,最先调用fnYe/fnBa/fnEr中的那一个函数?

答案:都行

IE5认为先调用fnEr,网景认为先调用fnYe,最后遇到了W3C

2002年,w3c发布标准
文档名为DOM Level 2 Events Specification
规定浏览器应该同时支持两种调用顺序
首先按照grandfather->father->son
然后按照son->father->grandfather

术语:

  • 从外向内找监听函数,叫做事件捕捉
  • 从内向外找监听函数,叫做事件冒泡
  • 那岂不是fnYe/fnBa/fnEr都调用两次,非也!
  • 开发者可以自己决定把fnYe放在捕捉阶段还是放在冒泡阶段
    在这里插入图片描述

addEventListener事件绑定API

IE5*:baba.attachEvent(‘onclick’,fn)//冒泡

网景:baba.addEventListener(‘click’,fn)//捕获

W3C:baba.addEventListener(‘click’,fn,bool)

如果bool不传或为falsy

就让fn走冒泡,即当浏览器在冒泡阶段发现baba有fn监听函数,就会调用fn,并提供时间信息。

如果bool为true

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

在这里插入图片描述

代码演示:

html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
<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>

</body>
</html>

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

level1.addEventListener('click', (e)=>{
  const t = e.currentTarget//e只有在点击得一瞬间才会出现,所以要用t来记录一下。
  setTimeout(()=>{  
    t.classList.remove('x')
  },n*1000)
  n+=1//因为如果每个时间都为1000那么就相当于在8点同时设置很多的闹钟,。知识
})
level2.addEventListener('click', (e)=>{
  const t = e.currentTarget
  setTimeout(()=>{  
    t.classList.remove('x')
  },n*1000)
  n+=1
})
level3.addEventListener('click', (e)=>{
  const t = e.currentTarget
  setTimeout(()=>{  
    t.classList.remove('x')
  },n*1000)
  n+=1
})
level4.addEventListener('click', (e)=>{
  const t = e.currentTarget
  setTimeout(()=>{  
    t.classList.remove('x')
  },n*1000)
  n+=1
})
level5.addEventListener('click', (e)=>{
  const t = e.currentTarget
  setTimeout(()=>{  
    t.classList.remove('x')
  },n*1000)
  n+=1
})
level6.addEventListener('click', (e)=>{
  const t = e.currentTarget
  setTimeout(()=>{  
    t.classList.remove('x')
  },n*1000)
  n+=1
})
level7.addEventListener('click', (e)=>{
  const t = e.currentTarget
  setTimeout(()=>{  
    t.classList.remove('x')
  },n*1000)
  n+=1
})

简化:

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)
level1.addEventListener('click',fa)
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)

知识复习:

classList

定义和用法

classList 属性返回元素的类名,作为 DOMTokenList 对象。

该属性用于在元素中添加,移除及切换 CSS 类。

classList 属性是只读的,但你可以使用 add() 和 remove() 方法修改它。

HTML DOM classList属性

currentTarget 事件属性

定义和用法
currentTarget 事件属性返回其监听器触发事件的节点,即当前处理该事件的元素、文档或窗口。
在捕获和起泡阶段,该属性是非常有用的,因为在这两个节点,它不同于 target 属性。

currentTarget 事件属性

总结:

两个疑问:

儿子被点击,算不算点击老子?

那么先调用老子得函数还是先调用儿子的函数?

捕获冒泡

捕获说先调用爸爸的监听函数

冒泡说先调用儿子的监听函数

W3C时间模型

先捕获(先爸爸=>儿子)再冒泡(再儿子=>爸爸)

注意e对象被传给所有的监听函数

事件结束后,e对象就不存在了

target v.s. currentTarget的区别

区别:

e.target - 用户操作的元素
e.currentTarget-程序员监听的元素
this是e.currentTarget,我个人不推荐使用它

举例:

div>span{文字},用户点击文字
e.target就是span
e.currentTarget就是div

一个特例

背景:

只有一个div被监听(不考虑父子同时被监听)

fn分别再捕获阶段和冒泡阶段监听click事件

用户点击的元素就是开发者监听的

代码:

div.addEventListenter('click',f1)
div.addEventListenter('click',f2,true)

请问,f1先执行还是f2先执行?

如果把两个调换位置?

总结:谁先监听谁先执行。

level7.addEventListener('click',()=>{
      console.log(2)
},true)//捕获
level7.addEventListener('click',()=>{
      console.log(1)
})//冒泡
e.stopPropagation():取消冒泡

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

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

注意:捕获不可以取消但是冒泡可以

不可以取消冒泡

有些事件不可以取消冒泡

可以查阅MDN英文版冒泡

比如scroll:

Bubbles:冒泡

Cancelable:是否取消冒泡

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值