📔
饥人谷前端体系课程笔记
黑马程序员笔记前端面试押题前端精进
  • 课程大纲
  • Git入门
    • 3. 软件安装详解
    • 4. Mac环境搭建
    • 5. 命令行入门
    • 6. 本地仓库
    • 7. Git远程仓库-GitHub
  • HTML全解
    • 8. HTML概览
    • 9. HTML标签
    • 10. HTML重难点
    • 11. HTML实践 & 手机调试
  • CSS全解
    • 12. CSS基础
    • 13. CSS布局(上)
    • 14. CSS布局(下)
    • 15. CSS定位
    • 16. CSS动画
  • HTTP全解
    • 17. URL 是什么
    • 18. 请求和响应 & Node.js Server
  • JS全解
    • 19. JavaScript概览
    • 20. 内存图与JS世界(精品课)
    • 21. Canvas 实践—画图板
    • 22. JS语法
    • 23. JS数据类型
    • 24. JS 对象
    • 25. JS 对象分类
    • 26. JS 数组
    • 27. JS 函数
    • 28. JS 实战,会动的代码
    • 29. JS运算符
    • 30. JS总结
  • JS编程接口
    • 31. DOM编程
    • 32. 手写DOM库
    • 33. JQuery中的设计模式(上)
    • 34. JQuery中的设计模式(下)
    • 35. DOM事件与事件委托
  • 项目 前端导航站点
    • 前端导航项目笔记
  • 前后分离
    • 40. AJAX的原理
    • 41. 异步与Promise
    • 42. 跨域、CORS、JSONP
    • 43. 静态服务器
    • 44. AJAX实战:Cookie、Session
  • JS进阶MVC
    • 48. MVC(上)
    • 49. MVC(中)
    • 50. MVC(下)
  • 项目构建
    • 51. Webpack(上)
    • 52. Webpack(下)
  • 算法与数据结构
    • 62. 伪代码与流程图
  • React全解
    • 75. React起手式
    • 76. React类组件和函数组件
    • 77. Class组件详解
    • 78. 函数组件
    • 79. Hooks原理解析
    • 80. Hooks各个击破
    • 81. 精通Redux
  • Node.js
    • 84. Node.js技术架构
    • 85. 文件模块
    • 86. 单元测试之文件模块
    • 87. 调试 Node.js 程序
    • 88. 静态服务器
    • 89. 命令行翻译工具
    • 90. 操作数据库
    • 91. 数据库基础知识
    • 92. Stream 流
    • 93. child_process
    • 94. 总结
  • TypeScript
    • 112. 基础
    • 113. 泛型
  • Next.js全解
    • Next.js 上
    • Next.js 下
  • ES6精讲
    • Promise,async/await
  • 大屏可视化笔记
    • 大屏可视化项目
  • SCSS全解
    • SCSS全解
  • 拓展
    • 一次性弄懂性能优化
    • Web性能优化
由 GitBook 提供支持
在本页
  • DOM 事件与事件委托
  • JS 编程接口
  • 点击事件
  • 和事佬 W3C
  • 示意图
  • addEventListener
  • 小结
  • target v.s. currentTarget
  • 一个特例
  • 取消冒泡
  • 不可阻止默认动作
  • 插曲:如何阻止滚动
  • 小结
  • 自定义事件
  • 事件委托
  • 封装事件委托
  • JS 支持事件吗

这有帮助吗?

  1. JS编程接口

35. DOM事件与事件委托

DOM 事件与事件委托

JS 编程接口

点击事件

从这个东西开始研究

代码

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

即 .爷爷>.爸爸>.儿子

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

  • 提问1:点击了谁

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

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

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

    • 答案:都算

  • 提问2:调用顺序

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

    • 答案:都行。

    • IE 5 认为先调 fnEr,网景认为先调 fnYe,然后掐上了

    • 最后闹到了 W3C

和事佬 W3C

  • 2002 年,W3C 发布标准

    • 文档名为 DOM Level 2 Events Specification

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

    • 首先 按 爷爷 => 爸爸 => 儿子 顺序看有没有函数监听

    • 然后 按 儿子 => 爸爸 => 爷爷 顺序看有没有函数监听

    • 有监听函数就调用,并提供事件信息,没有就跳过

  • 术语

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

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

    • 疑问:那岂不是 fnYe / fnBa / fnEr 都调用两次?非也!

    • 开发者 自己选择 把 fnYe 放在 捕获阶段 还是放在冒泡阶段

示意图

addEventListener

  • 事件绑定 API

    • IE 5*:baba.attachEvent('onclick', fn) // 冒泡

    • 网景:baba.addEventListener('click',fn) // 捕获

    • W3C:baba.addEventListener('click', fn, bool)

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

  • 如果 bool 为 true

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

*2020 之后, 永远 不要学习关于 IE 5、6、7、8、9、10、11 的知识,用到再搜

你可以选择把 fn 放在哪边

代码示例

代码图解

level1.addEventListener('click', (e)=>{//这里的e对象在点击执行后就会消失,因此需要一个变量来保存这个值
  const t = e.currentTarget;
  setTimeout(()=>{
    t.classList.add('x')
  },n * 1000)
	n += 1
},true)//如果这里加true,意味着使用捕获模式,如果不加就是冒泡模式

小结

  • 两个疑问

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

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

  • 捕获与冒泡

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

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

  • 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.addEventLisenter('click', f1)

    • div.addEventLisenter('click', f2, true)

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

    • 如果把两行调换位置后,请问哪个先执行?

    • 错误答案:f2 先执行

    • 正确答案:谁先监听谁先执行

    • 总结:这是一个特例

取消冒泡

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

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

    • 通俗来说:有人打我,我自己解决,别告诉我老子

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

不可阻止默认动作

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

    • MDN 搜索 scroll event,看到 Bubbles 和 Cancelable

    • Bubbles 的意思是该事件是否冒泡

    • Cancelable 的意思是开发者是否可以取消冒泡

    • Cancelable 与冒泡无关

    • 推荐看 MDN 英文版,中文版内容不全

插曲:如何阻止滚动

  • scroll 事件 不可阻止默认动作

    • 阻止 scroll 默认动作没用,因先有滚动才有滚动事件

    • 要阻止滚动,可阻止 wheel 和 touchstart 的默认动作

    • 但是滚动条还能用,可用 CSS 让滚动条 width: 0

    • //禁用滚轮
      x.addEventListener('wheel',(e)=>{
        e.preventDefault()
      })
      //隐藏滚动条
      ::-webkit-scrollbar{
        width:0 !important
      }
      //禁用触屏滚动
      x.addEventListener('touchstart',(e)=>{
        e.preventDefault()
      })
  • CSS 也行

    • 使用 overflow: hidden 可以直接取消滚动条

    • 但此时 JS 依然可以修改 scrollTop

小结

  • target 和 currentTarget

    • 一个是用户点击的,一个是开发者监听的

  • 取消冒泡

    • e.stopPropagation()

  • 事件的特性

    • Bubbles 表示是否冒泡

    • Cancelable 表示是否支持开发者取消冒泡

    • 如 scroll 不支持取消冒泡

  • 如何禁用滚动

    • 取消特定元素的 wheel 和 touchstart 的默认动作

自定义事件

  • 浏览器自带事件

  • 提问

    • 开发者能不能在自带事件之外,自定义一个事件

button1.addEventListener('click',()=>{
	const event = new CustomEvent('frank',{
    detail: {name:'frank', age:18},
    bubbles:true,
    cancelable:false;
  })
  button1.dispatchEvent(event)
})
button1.addEventListener('frank', (e)=>{
  console.log(e.detail)
})

事件委托

  • 场景一

    • 你要给 100 个按钮添加点击事件,咋办?

    • 答:监听这 100 个按钮的祖先,等冒泡的时候判断 target 是不是这 100 个按钮中的一个

      div>button{click $}*100
    <button>click 1</button>
    div1.addEventListener('click',(e)=>{
      const t = e.target
      if(t.tagName.toLowerCase() === 'button'){
        console.log('button被点击了');
        console.log('button内容是'+t.textContent)
        console.log('button data-id'+t.dataset.id)
      }
    })

    代码示例:

  • 场景二

    • 你要监听目前不存在的元素的点击事件,咋办?

    • 答:监听祖先,等点击的时候看看是不是我想要监听的元素即可

  • <div id="div1"></div>
    setTimeout(()=>{
      const button = document.createElement('button')
      button.textContent = 'click 1'
      div1.appendChild(button);
    }, 1000)
    
    div1.addEventListener('click',()=>{
      const t = e.target
      if(t.tagName.toLowerCase() === 'button'){
        console.log('button被click')
      }
    })
  • 优点

    • 省监听数(内存)(就是省内存)

    • 可以监听动态元素(比如一开始元素不存在)

    on('click','#div1','button',()=>{
      console.log('button被点击了')
    })
    
    function on(eventType, element, selector, fn){
      if(!(element instanceof Element)){
         element = document.querySelector(element);
         }  
      element.addEventListener(eventType, (e)=>{
        const t = e.target;
        if(t.matches(selector)){
          fn(e)
        }
      })
    }

封装事件委托

  • 要求

    • 写出这样一个函数 on('click', '#testDiv', 'li', fn)

    • 当用户点击 #testDiv 里的 li 元素时,调用 fn 函数

    • 要求用到事件委托

  • 答案一

    • 判断 target 是否匹配 'li'

  • 答案二

  • 整合进 jQuery

    • 有兴趣可以自己实现 $('#xxx').on('click', 'li', fn)

JS 支持事件吗

  • 答

    • 支持,也不支持。本节课讲的 DOM 事件不属于 JS 的功能,术语浏览器提供的 DOM 的功能

    • JS 只是调用了 DOM 提供的 addEventListener 而已

  • 追问

    • 如何当 JS 支持事件?请手写一个事件系统。

    • 目前大家的水平还写不出来,可以先思考一段时间。

希望你对 DOM 事件,有一个完整的了解

上一页34. JQuery中的设计模式(下)下一页项目 前端导航站点

最后更新于2年前

这有帮助吗?

截屏2023-04-11 下午6.09.47

如果 bool 不传或为

image-20230411184838817
image-20230411184847355

注意你需要找准滚动条所在的元素,

一共100多种事件,在 MDN 上

答案:可以,见

target / target的爸爸 / target的爷爷

falsy
知乎:e.target与e.currentTarget区别整理
示例
列表
示例
JS Bin on jsbin.com
递归判断