前端|关于Js事件的一切(事件冒泡 事件捕获 事件循环 事件代理 事件监听等)

【前端|关于Js事件的一切(事件冒泡 事件捕获 事件循环 事件代理 事件监听等)】在本文中将阅读到的内容有:
  1. 什么是事件
  2. 事件监听
  3. 事件冒泡 事件捕获
  4. 事件对象
  5. 事件代理
  6. 事件发布订阅
1. 什么是事件 在介绍事件冒泡捕获等理论之前,我们首先要明确什么是事件。
事件是您在编程时系统内发生的动作或者发生的事情——系统会在事件出现时产生或触发某种信号,并且会提供一个自动加载某种动作(列如:运行一些代码)的机制。
在 Web 中, 事件在浏览器窗口中被触发并且通常被绑定到窗口内部的特定部分 — 可能是一个元素、一系列元素、被加载到这个窗口的 HTML 代码或者是整个浏览器窗口。
几个比较常见的事件举例
  • 用户在某个元素上点击鼠标或悬停光标。
  • 用户在键盘中按下某个按键。
  • 用户调整浏览器的大小或者关闭浏览器窗口。
  • 一个网页停止加载。
  • 提交表单。
  • 播放、暂停、关闭视频。
更多事件类型可以参考这里
2. 事件监听 2.1 方法一
每个可用的事件都会有一个事件监听器,监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。
举例说明:
假设界面上有一个

const btn = document.querySelector('button'); function random(number) {return Math.floor(Math.random()*(number+1)); }btn.onclick = function() {const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; document.body.style.backgroundColor = rndCol; }

我们使用 btn 变量存储 button,并使用了Document.querySelector() 函数。我们也定义了一个返回随机数字的函数。代码第三部分就是事件处理器。btn变量指向 button 元素,在 button 这种对象上可触发一系列的事件,因此也就可以使用事件处理器。我们通过将一个匿名函数(这个赋值函数包括生成随机色并赋值给背景色的代码)赋值给“点击”事件处理器参数,监听“点击”这个事件。
只要点击事件在元素上触发,该段代码就会被执行。即每当用户点击它时,都会运行此段代码
这个btn.onclick还有许多
  • btn.onfocus及btn.onblur —颜色将于按钮被置于焦点或解除焦点时改变(尝试使用Tab移动至按钮上,然后再移开)。这些通常用于显示有关如何在置于焦点时填写表单字段的信息,或者如果表单字段刚刚填入不正确的值,则显示错误消息。
  • btn.ondblclick — 颜色将仅于按钮被双击时改变。 window.onkeypress, window.onkeydown,
  • window.onkeyup — 当按钮被按下时颜色会发生改变. keypress 指的是通俗意义上的按下按钮 (按下并松开), 而keydown 和 keyup 指的是按键动作的一部分,分别指按下和松开. 注意如果你将事件处理器添加到按钮本身,它将不会工作 —我们只能将它添加到代表整个浏览器窗口的 window对象中。
  • btn.onmouseover 和 btn.onmouseout —颜色将会在鼠标移入按钮上方时发生改变, 或者当它从按钮移出时. 一些事件非常通用,几乎在任何地方都可以用(比如 onclick 几乎可以用在几乎每一个元素上),然而另一些元素就只能在特定场景下使用,比如我们只能在 video 元素上使用 onplay 。
2.2 方法二 addEventListener() 和removeEventListener()
2.2.1 基本使用方法
新的事件触发机制被定义在 Document Object Model (DOM) Level 2 Events Specification, 这个细则给浏览器提供了一个函数 — addEventListener()。这个函数和事件处理属性是类似的,但是语法略有不同。我们可以重写上面的随机颜色背景代码:
const btn = document.querySelector('button'); function bgChange() {const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; document.body.style.backgroundColor = rndCol; }btn.addEventListener('click', bgChange); //清除不使用的事件处理器 btn.removeEventListener('click', bgChange);

您也可以给同一个监听器注册多个处理器,下面这种方式不能实现这一点:
myElement.onclick = functionA; myElement.onclick = functionB;

第二行会覆盖第一行,但是下面这种方式就会正常工作了:
myElement.addEventListener('click', functionA); myElement.addEventListener('click', functionB);

当元素被点击时两个函数都会工作。
2.2.2 addEventListener() 详解
EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element,Document和Window或者任何其他支持事件的对象 (比如 XMLHttpRequest)。
语法:
target.addEventListener(type, listener, options); target.addEventListener(type, listener, useCapture);

参数说明:
type 表示监听事件类型的字符串(详细事件类型参考)
listener 当所监听的事件类型触发时,会接收到一个事件通知(实现了 Event 接口的对象)对象。listener 必须是一个实现了 EventListener 接口的对象,或者是一个函数。
options 可选 一个指定有关 listener 属性的可选参数对象。可用的选项如下:
  • capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
  • once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
  • passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。查看 使用 passive 改善的滚屏性能 了解更多.
  • signal:AbortSignal,该 AbortSignal 的 abort() 方法被调用时,监听器会被移除。
useCapture 可选 Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。 如果没有指定, useCapture 默认为 false 。
注意:
对于事件目标上的事件监听器来说,事件会处于“目标阶段”,而不是冒泡阶段或者捕获阶段。在目标阶段的事件会触发该元素(即事件目标)上的所有监听器,而不在乎这个监听器到底在注册时useCapture
参数值是true还是false。
使用这些参数的更多列子参考这里
3. 事件冒泡 事件捕获 在页面中点击一个元素,事件是从这个元素的祖先元素中逐层传递下来的,这个阶段为事件的捕获阶段。当事件传递到这个元素之后,又会把事件逐成传递回去,直到根元素为止,这个阶段是事件的冒泡阶段。
事件捕获阶段:
前端|关于Js事件的一切(事件冒泡 事件捕获 事件循环 事件代理 事件监听等)
文章图片

事件冒泡阶段:
前端|关于Js事件的一切(事件冒泡 事件捕获 事件循环 事件代理 事件监听等)
文章图片

4. 事件对象 有时候在事件处理函数内部,您可能会看到一个固定指定名称的参数,例如event,evt或简单的e。 这被称为事件对象,它被自动传递给事件处理函数,以提供额外的功能和信息。 例如,让我们稍稍重写一遍我们的随机颜色示例:
function bgChange(e) {const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; e.target.style.backgroundColor = rndCol; console.log(e); }btn.addEventListener('click', bgChange);

event.target vs event.currentTarget
举例说明这俩者的区别:
> document.getElementById('a').addEventListener('click', function(e) {console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id); }); document.getElementById('b').addEventListener('click', function(e) {console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id); }); document.getElementById('c').addEventListener('click', function(e) {console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id); }); document.getElementById('d').addEventListener('click', function(e) {console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id); });

上面事件的绑定都是在冒泡阶段的(因为addEventListener useCapture默认为false,代表在冒泡阶段绑定),当我们点击最里层的元素d的时候,会依次输出:
target:d¤tTarget:d target:d¤tTarget:c target:d¤tTarget:b target:d¤tTarget:a

从输出中我们可以看到,event.target指向引起触发事件的元素,而event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的event.target才会等于event.currentTarget。
如果我们把事件都绑定在捕获阶段,然后还是点击最里层的元素d,这时event.target还依旧会指向d,因为那是引起事件触发的元素,因为event.currentTaget指向事件绑定的元素,所以在捕获阶段,最先来到的元素是a,然后是b,接着是c,最后是d。所以输出的内容如下:
target:d¤tTarget:a target:d¤tTarget:b target:d¤tTarget:c target:d¤tTarget:d

preventDefault()
有时,你会遇到一些情况,你希望事件不执行它的默认行为。 最常见的例子是Web表单,例如自定义注册表单。 当你填写详细信息并按提交按钮时,自然行为是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种“成功消息”页面(或 相同的页面,如果另一个没有指定。)
使用preventDefault()可以阻止默认行为。
stopPropagation()
准事件对象具有可用的名为 stopPropagation()的函数, 当在事件对象上调用该函数时,它只会让当前事件处理程序运行,但事件不会在冒泡链上进一步扩大,因此将不会有更多事件处理器被运行(不会向上冒泡)。
5. 事件代理(委托) 冒泡还允许我们利用事件委托——这个概念依赖于这样一个事实,如果你想要在大量子元素中单击任何一个都可以运行一段代码,您可以将事件监听器设置在其父节点上,并让子节点上发生的事件冒泡到父节点上,而不是每个子节点单独设置事件监听器。
一个很好的例子是一系列列表项,如果你想让每个列表项被点击时弹出一条信息,您可以将click单击事件监听器设置在父元素