DOM 事件进阶——事件流,事件对象和事件委托

事件监听
-
解释:
一旦行为或状态发生改变,便立即调用一个函数。 -
完成事件监听分成3个步骤:
-
获取 DOM 元素
-
通过
addEventListener
方法为 DOM 节点添加事件监听 -
等待事件触发,如用户点击了某个按钮时便会触发
click
事件类型 -
事件触发后,相对应的回调函数会被执行
-
如果将函数 A 做为参数传递给函数 B 时,我们称函数 A 为回调函数。
两种绑定方式区别
使用 on
方式
这种方式是公元 2000 年之前的普遍用法,处在 DOM 事件模型的 Level 0 阶段 ~ Level 1 阶段。L1 阶段并没有多少改进,只是将 L0 规范化。
1 | element.onclick = function () { |
-
一个事件只能绑定一个处理器,新绑定会覆盖旧的。
-
没有事件捕获(只有冒泡)。
-
兼容性最好,但功能最弱。
使用事件监听器
这是 L2 阶段的用法。
1 | element.addEventListener('click', function (e) { |
-
支持事件 捕获阶段(capture)和冒泡阶段(bubble)。
-
提供更丰富的事件对象(如
e.target
、e.stopPropagation()
等)。 -
跨浏览器更一致(IE9+ 开始兼容)。
L3 阶段(2004 年)
增加了第三个参数对象 { once, passive, capture }
:
1 | element.addEventListener('scroll', handler, { |
-
once: true
:事件监听器只执行一次,执行后自动移除。 -
passive: true
:用于性能优化,比如滚动事件。 -
capture: true
:明确处于捕获阶段。
事件解绑
对于 L0:
1 | element.onclick = null; |
对于 L2 和现代:
1 | element.removeEventListener('click', fn); |
常见的事件类型
鼠标事件
- 单击
click
,双击dbclick
- 移入
mouseenter
&mouseover
(冒泡),移出mouseleave
&mouseout
(冒泡)
mouseover
和mouseout
两个 O 会冒泡
键盘事件
keydown
键盘按下触发,keyup
键盘抬起触发
通常情况用 keyup
更合适一点,因为有些人喜欢按着不动。
焦点事件
focus
获得焦点,blur
失去焦点
文本框输入事件
input
,在 textarea 这样的输入区变一下就会触发一次。常用来做输入区字符统计,例如:
1 | function updateCountNum() { |
页面加载事件
load
事件
表示某个元素加载好以后触发。这个可以用于 window,表示整个页面,也可以用于各种媒体元素,比如 <img>
, <script>
, <link>
, <iframe>
等,但是不能用于 document。
🌟常见用法:
- 等待图片加载好以后执行回调函数:
1 | const img = document.querySelector("img"); |
- 等所有元素加载好以后,执行回调函数:
DOMContentLoaded
事件1 | window.addEventListener("load", () => { |
DOMContentLoaded
事件
等待 DOM 加载完成后,就触发此事件,而无需等待 CSS,图片以及其他外部资源。
什么是“DOM 加载完成”?
DOM 就绪(DOMContentLoaded):指的是浏览器已经完成 HTML 文档的解析,构建好了 DOM 树,此时你可以安全地通过 JS 操作 HTML 元素了。
举例:
对于这样的结构:
1 | <body> |
这段代码被浏览器解析完成后,可能生成这样的结构:
1 | Document |
注意:此时 img 的 <img>
标签存在了,但图片 a.jpg
还可能在加载中,但我们已经能通过 JS 获取 img
元素了。
这个事件一般只用于 document
对象。
页面滚动事件
事件对象
回调函数可以加一个参数,用于拿到元素的事件对象。可以在控制台打印一下这个对象,看看都有些什么信息:
1 | commentBox.addEventListener("keyup", function (e) |
值得注意的是,不同的事件类型的事件对象也是不同的。
例如,我对 keyup
事件可以监听到按下的值,来完成“回车键+Ctrl”键发布评论的逻辑。这个对于 input
事件,则对象里没有 key 属性:
1 | commentBox.addEventListener("keyup", function (e) |
环境对象
环境对象指的是函数内部特殊的变量 this
,它代表着当前函数运行时所处的环境。
this
本质上是一个变量,数据类型为对象- 函数的调用方式不同
this
变量的值也不同 - 【谁调用
this
就是谁】是判断this
值的粗略规则
事件流
事件捕获和事件冒泡
-
addEventListener
第3个参数决定了事件是在捕获阶段触发还是在冒泡阶段触发。所以捕获和冒泡只会触发其中一个。 -
addEventListener
第3个参数为true
表示捕获阶段触发,false
表示冒泡阶段触发,默认值为false
1 | outer.addEventListener('click', function () { |
-
事件流只会在父子元素具有相同事件类型时才会产生影响
-
绝大部分场景都采用默认的冒泡模式(其中一个原因是早期 IE 不支持捕获,也就是 Level0 的 onclick)
事件委托
作用
利用事件委托,可以避免给每一个子元素都添加监听器,而是给父元素加一个监听器。当子元素触发了事件的时候,会冒泡到父元素,父元素可以利用 e.target
确认出是哪一个子元素触发了事件。
- 一个小案例:
鼠标进入 ul 的 tab 栏,判断到底是在哪一个 li ,来显示对应内容:
技巧:筛选指定元素
就拿上面那个案例说,在 ul 里,除了点到 li,有可能鼠标还会点到 ul 内,li 外的空白区域。
亦或者在一些父元素内,仅仅希望某些子元素被触发。
我们知道,e.target
是一个 DOM 元素(即事件实际触发的目标节点),它是一个 Element
对象,因此具有所有 DOM 元素的属性和方法,比如标签名。
通过 tagName
我们可以通过这种方法筛选掉除了 a 标签以外的元素触发的事件:
1 | if (e.target.tagName === "A") { |
常见的 DOM 元素的属性和方法,我们可见:
DOM 元素的常见属性和方法
更现代化的做法是用这几种:
matches()
、closest()
、dataset
closest() 向祖先查找
closest()
方法返回当前元素或最近的(祖先)元素中,匹配选择器的第一个元素(如果没有,返回 null
)
1 | // 返回找到的元素,或者 NULL |
matches() 精确匹配选择题
matches()
用于判断某个元素是否和指定的 CSS 选择器匹配。
1 | // 返回布尔值 |
阻断事件流
使用 stopPropagation()
方法可以在事件流的任意阶段阻断流动
该方法应该由事件对象调用。
1 | document.addEventListener('click', function (e) { |
阻止默认行为
同样的,还可以给事件对象添加 preventDefault()
方法,来阻止其的一些默认行为。常见的做法:提交表单前进行合法性校验。
下面这个案例,会阻止超链接元素的默认点击跳转行为,从而避免你用上某款低质量搜索引擎。
1 | <a href="http://www.baidu.com" style="font-size: large">百度一下</a> |
- 标题: DOM 事件进阶——事件流,事件对象和事件委托
- 作者: 三葉Leaves
- 创建于 : 2025-05-20 00:00:00
- 更新于 : 2025-05-31 13:43:53
- 链接: https://blog.oksanye.com/d948da33c49e/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。