JavaScript 事件狂想曲!无干扰捕捉*所有*事件

Avatar of Ghostlab
Ghostlab

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 价值200美元的免费积分!

以下是 Matthias Christen 和 Florian Müller 来自 Ghostlab 的客座文章。Ghostlab 是适用于 Mac 和 PC 的跨浏览器跨设备测试软件。我非常印象深刻的一点是 Ghostlab 可以将一个浏览器的事件同步到所有其他浏览器。滚动一个页面,其他你正在测试的页面也会滚动。在一个页面上点击某个地方,相同的点击会在其他页面上发生。我问了他们,当有这么多可能干扰因素时,它究竟是怎么做到的。Matthias 和 Florian 解释道

我们一直在开发 Ghostlab,它是一款用于跨设备和跨浏览器测试网站的工具。本质上,它会同步查看网站的任何数量的客户端,并使您能够轻松地浏览该网站,并确保它在所有视窗和平台上都能正常工作且看起来很好。一个核心组件是跨客户端的事件复制——当用户在一个客户端上点击按钮、滚动或在表单字段中输入文本时,我们必须确保在所有其他客户端上也发生完全相同的事情。

捕捉漏掉的事件

Ghostlab 的客户端脚本组件正在监听所有发生的事件,尝试捕捉它们,并将它们复制到所有其他客户端。在某个时候,我们注意到我们并没有完全捕捉到所有事件。我们必须弄清楚问题出在哪里,并找到了一个解决方案,它允许您捕捉到您网站上发生的任何事件,无论它们如何被任何自定义 JavaScript 处理。

怎么可能您正在监听一个事件,但没有捕捉到它呢?这是因为任何事件处理程序都可以选择对一个事件执行多种操作。您会知道阻止浏览器通常执行的默认操作的能力(preventDefault())。例如,它允许您有一个链接(<a>),对于该链接,用户不会在 click 事件上转到其 href

除了告诉浏览器在每次事件发生时不要执行默认操作外,事件处理程序还可以阻止事件传播。当在一个元素(比如一个链接)上触发一个事件时,任何附加到此特定元素的事件处理程序将被允许首先处理该事件。处理完后,事件将向上冒泡,直到到达 document 级别。此事件在原始元素的任何父元素上的任何监听器都将能够对该事件做出反应——除非较低级别的事件处理程序决定停止传播,在这种情况下,事件将不再向上遍历 DOM。

我们的 示例 1 演示了这一点。当您点击内部 div(Level 3)时,此元素的点击处理程序将首先处理该事件。除非它阻止传播,否则父元素(Level 2、Level 1)随后将能够按顺序对该事件做出反应。如果您选中了“停止传播”复选框,事件处理程序将阻止进一步传播——因此,Level 3 上的点击事件将不再到达 Level 1 和 Level 2,Level 2 上的点击事件将不再到达 Level 1。

查看 CodePen 上 Florian Mueller (@mueflo00) 的 事件传播示例 I

示例 2 中,我们演示了停止立即传播的效果。此方法隐式地阻止了事件的冒泡,因此如果有任何父元素,我们会观察到与示例 1 相同的行为。此外,它还阻止了在同一元素上对同一事件的任何其他处理程序执行。在我们的示例中,我们必须在我们的元素上注册点击事件处理程序。如果我们选择停止立即传播,只有第一个响应者将能够处理该事件,并且在调用 stopImmediatePropagation 之后,将不再调用任何其他处理程序。

查看 CodePen 上 Florian Mueller (@mueflo00) 的 事件传播示例 II

因此,如果您想监听 DOM 中发生的 tất cả 事件,这非常困难。为了防止由于取消冒泡而导致的事件丢失,您必须在 DOM 的每个元素上注册所有事件处理程序。即使这样,如果开发人员选择停止立即传播,这也只有在您是第一个注册该事件的人时才会起作用。

如果我们想要绝对确定我们被告知任何事件,无论其处理程序对其做了什么,我们都必须在事件注册的开始阶段进入循环。为此,我们覆盖了 EventTarget 对象的 addEventListener 函数。基本思路很简单:任何事件处理程序注册最终都会调用此方法。如果我们覆盖它,我们就完全控制了任何事件处理程序注册时发生的事情。

原始 addEventListener 函数将事件处理程序函数(“原始事件处理程序”)作为其第二个参数。如果我们不覆盖 addEventListener 函数,则原始事件处理程序函数将在每次发生指定事件时被调用。现在,在我们自定义的 addEventListener 函数中,我们只需将原始事件处理程序包装在我们自己的事件处理程序(“包装器函数”)中。包装器函数包含我们需要的任何逻辑,并最终可以调用原始事件处理程序——如果我们希望这样做。

示例 3 演示了这一点。“Level” 元素上附加的三个点击事件是通过我们自定义的 addEventListener 函数注册的,因此每当在这些元素上发生点击事件时,我们的包装器函数就会被调用。在那里,我们观察复选框的状态——如果它被选中,我们 simply 不调用原始事件处理程序,从而阻止任何点击事件触发任何原始事件处理程序。一个小小的注意事项:如果您想确保您控制所有事件,您必须确保在注册任何事件之前覆盖 addEventListener 函数。

查看 CodePen 上 Florian Mueller (@mueflo00) 的 事件覆盖示例

虽然这个解决方案帮助我们改进 Ghostlab,但您可能会问自己,这有什么用呢?好吧,有几种可能性。我们下面概述了两种可能的用例——如果您能想出其他用例,请分享!

可能的应用 1:事件可视化器

有一些工具可以帮助您可视化网站上的事件(例如,我们非常喜欢 VisualEvent Allan Jardine)。使用我们的技术,我们可以快速地自己实现这样的工具。在我们的示例中,对于每个注册的事件,我们 simply 在注册该事件的元素的顶部绘制一个小方块。悬停时,我们会显示(原始)注册的事件处理程序函数的源代码。

查看 CodePen 上 Florian Mueller (@mueflo00) 的 事件可视化器

可能的应用 2:事件统计

您也可以以其他方式显示事件指标,而不是将它们绘制到屏幕上。此示例向您展示了任何页面上注册事件的表格概述(假设您将代码注入其中),并实时更新触发的事件。例如,当您遇到性能问题并怀疑可能是因为事件处理程序过多时,这会很有用。

查看 CodePen 上 Florian Mueller (@mueflo00) 的 事件覆盖:统计


事件只是前端开发巨大而复杂世界中的一小部分。在 Vanamco,我们很高兴能为您提供帮助简化和简化您的流程的工具,同时让您了解最佳实践。