6-6 JS编程接口(点击事件、addEventListener、捕获与冒泡、阻止滚动)

DOM事件与事件委托

高频面试题目

1.点击事件

  • 代码
<div class="爷爷">
  <div class="爸爸">
    <div class="儿子">
      文字
    </div>
  </div>
</div>
  • 给三个div分别添加时间监听 fnYe/fnBa/fnEr
  • 提问1:点击了谁?
    1.点击文字,算不算点击儿子?
    2.点击文字,算不算点击爸爸?
    3.点击文字,算不算点击爷爷?
    答:都算
  • 提问2:调用顺序
    点击文字,最先调用fnYe/fnBa/fnEr哪个函数?
    答:都行(对于顺序IE5和网景有争论,由W3C发布规矩)

2.调用顺序

2.1 W3C发布标准(函数调用顺序)

  • 文档明为DOM Level 2 Event Specifition
  • 规定浏览器应该支持两种调用顺序
  • 首先爷爷=>爸爸=>儿子 顺序看有没有函数监听
  • 然后儿子=>爸爸=>爷爷 顺序看有没有函数监听
  • 从外到内从内到外
  • 有监听函数就调用,并提供时间信息,没有就跳过

2.2 术语

  • 从外向内找监听函数,叫事件捕获
  • 从内向外找监听函数,叫事件冒泡

2.3 提问:那岂不是 fnYe / fnBa / fnEr 都调用两次?非也!
解决:开发者自己选择把监听函数放到捕获阶段还是冒泡阶段
6-6 JS编程接口(点击事件、addEventListener、捕获与冒泡、阻止滚动)
3.addEventListener

  • 事件绑定API
    IE5*:baba.attachEvent('onclick',fn)//冒泡
    网景:baba.addEventListener('click',fn)//捕获
    W3C:baba.addEventListener('click',fn,bool)(bool参数用来选择是冒泡还是捕获,如果不填bool就选择IE的冒泡
  • 如果 bool 不填或为 falsy(类似false的值)
    就让 fn 走冒泡,即当浏览器在冒泡阶段发现 baba 有 fn 监听函数,就会调用 fn,并提供时间信息(W3C默认偏向IE,因为经常默认不填bool)
  • 如果 bool 为 true
    就让 fn 走捕获,即当浏览器在捕获阶段发现 baba 有 fn 监听函数,就会调用 fn,并提供时间信息
  • 注意:捕获和冒泡都是一定要走的,只是决定在哪里执行函数

4.案例
6-6 JS编程接口(点击事件、addEventListener、捕获与冒泡、阻止滚动)

  • 代码实现:颜色从内往外依次出现
    原理是用 x 设置了背景透明,然后依次删掉每个level的x
  • JS渲染是一个很快的过程,每一个间隔的操作只相差很短的时间
  • 背景透明(CSS)
    .x{background: transparent};
  • fn的参数e只在点击的一瞬间存在,点击完了之后参数就不存在了
  • 解决:const 一个变量 t,令 t=e.currentTarget
    这样,即使点击一瞬间e消失了,t仍然存在
  • 如果几个level的setTimeout时间都是一样是1000的话,就相当于在1s后添加了7个闹钟,想要依次响起就令为 n*1000 然后 n+=1
  • 如果没有传入 bool 参数,则默认冒泡顺序,也就是执行顺序是从内往外
  • 如果想要捕获的过程:则每个都添加一个 true
  • 想要两个一起看,则绑定两个事件,则一共14个监听函数
    先是捕获(从外到内),再是冒泡(从内到外)
let n=1
level1.addEventListener('click',(e)=>{
   const t = e.currentTarget
   setTimeout(()=>{
     t.classList.remove('x')//删掉x
   }, n*1000)
})//没有加 bool 参数默认为冒泡顺序,从里向外
level1.addEventListener('click',(e)=>{
   const t = e.currentTarget
   setTimeout(()=>{
     t.classList.add('x')//添加上刚刚删掉的x
   },n*1000)
},true)//捕获顺序
  • 代码优化
    将重复的代码取一个名字。(比如:给个函数,然后调用它即可)
let n=1
const fn = (e)=>{
   const t = e.currentTarget
   setTimeout(()=>{
     t.classList.remove('x')//删掉x
   }, n*1000)
}
const fm = (e)=>{
   const t = e.currentTarget
   setTimeout(()=>{
     t.classList.add('x')//添加上刚刚删掉的x
   },n*1000)
}
level1.addEventListener('click',fn)
level1.addEventListener('click',fm,ture)

4.1 代码图解

  • 每次点击都会从 window ~ 文字,然后从 文字 ~ window
  • 进入的时候发现 level1 上面有个函数,就执行,从f1-f7,然后从f7~f1
  • 可以每个上面都绑定同一个函数

6-6 JS编程接口(点击事件、addEventListener、捕获与冒泡、阻止滚动)
5.小结

5.1两个疑问

  • 儿子被点击了,算不算点击了老子?
    答:算,只要div里面任何一部分被点击了,这个div就算被点击了
  • 那么先调用的老子的函数还是先调用儿子的函数?
    答:不一定

5.2 捕获与冒泡

  • 捕获说:先调用爸爸的监听函数
  • 冒泡说:先调用儿子的监听函数

5.3 W3C 事件模型

  • 先捕获(先爸爸=>儿子),再冒泡(再儿子=>爸爸)
    注意:后面可以阻止冒泡
  • 注意e对象被传给所有监听函数
  • 事件结束后,e对象就不存在了

6.target V.S. currentTarget

6.1 区别

  • e.target - 用户在操作的元素
    e对象的属性 target
  • e.currentTarget - 程序员监听的函数
  • this是 e.currentTarget ,监听代码里不推荐使用它

6.2 举例

  • div > span{文字},用户点击文字
  • e.taeget 就是span
    因为用户点的是 span 上的文字
  • e.currentTarget 就是div
    程序员监听的是div,div.onclick

7.一个特例

  • 只有一个 div 被监听(不考虑父子同时被监听)
  • fn 分别在捕获阶段和冒泡阶段监听click时间
  • 用户点击的就是监听的元素,没有爸爸和儿子的包含关系,那么谁先监听谁就先执行,没有父子。
level7.addEventListener('click',()=>{
  console.log(2)
},true)//捕获
level7.addEventListener('click',()=>{
  console.log(1)
})//冒泡

8.取消冒泡

  • 捕获不能取消,但是冒泡可以
  • e.stopPropagation()可以中断冒泡,浏览器不再往上走
  • 通俗来说:有人打我,我自己解决,别告诉我老子
  • 一般用于封装独立组件
level4.addEventListener('click',(e)=>{
  e.stopPropagation()
  fm(e)
})

9.不可取消冒泡

  • 有些事件不可(不支持)取消冒泡
  • MDN 搜索 scroll event(滚动事件),看到 Bubbles 和 Cancelable
  • Bubbles 的意思是该事件是否冒泡
  • Cancelable 的意思是开发者是否可以取消冒泡
    6-6 JS编程接口(点击事件、addEventListener、捕获与冒泡、阻止滚动)
    6-6 JS编程接口(点击事件、addEventListener、捕获与冒泡、阻止滚动)

10.如何阻止滚动

10.1 scroll 事件不可取消冒泡

  • 阻止 scroll 默认动作没用,因为现有滚动才有滚动事件
  • 要阻止滚动,可以阻止 wheel(滚轮滚动) 和 touchstart(手机触屏) 的默认动作
  • 注意你需要找准滚动条所在的元素
    要找到能覆盖整个区域的元素
  • 但是滚动条鼠标点击还能用,可用CSS让滚动条 width: 0

10.2 CSS也行

  • 使用 overflow: hidden 可以直接取消滚动条
  • 但此时 JS 依然可以修改 scrollTop
::-webkit-scrollbar { width:0 !important}
x.addEventListener('wheel',(e)=>{//x为body的id
   e.preventDfault()
})
x.addEventListener('touchstart',(e)=>{
   e.preventDfault()
})

11 小结

  • target 和 currentTarget
    一个是用户点击的,一个是开发者监听的
  • 取消冒泡
    e.stopPropagation()
  • 事件的特性
    Bubbles 表示是否冒泡
    Cancelable 表示是否支持开发者取消冒泡
    如 scroll 不支持冒泡
  • 如何禁用滚动
    取消特定元素的 wheel 和 touchstart 的默认动作
    而不是阻止冒泡
上一篇:JavaScript - DOM事件的优化


下一篇:生成1~n的排列,以及生成可重集的排列