写点什么

注入 js 编程应该知道的一些基础知识,以防混淆造成的错误

作者:Geek_ee6d52
  • 2024-03-18
    日本
  • 本文字数:5239 字

    阅读完需:约 17 分钟

假设我现在用的是chrome extension的方式注入javascript来控制网页,例如有一个元素,原始的网页制作者已经给他注册了各种事件,但是我想得到当这个元素发生改变时,会触发一个我自己能控制的事件,有没有什么办法?
复制代码

要在不干扰原有网页事件的前提下,为元素添加自定义事件处理程序,你可以使用 MutationObserver API 或者通过事件委托的方式来实现。

使用 MutationObserver

MutationObserver是一个可以监听 DOM 树变化的接口,适用于监听元素属性、子节点等的变化。如果你想监听某个元素的变化(如属性、子节点等),可以使用这个 API。

以下是一个简单的例子,展示如何使用MutationObserver来监听元素的变化,并在变化时执行自定义逻辑:

// 选择目标节点var targetNode = document.getElementById('target');// 配置观察者选项:var config = { attributes: true, childList: true, subtree: true };// 当观察到变化时执行的回调函数var callback = function(mutationsList, observer) {    for(var mutation of mutationsList) {        if (mutation.type == 'childList') {            console.log('A child node has been added or removed.');        }        else if (mutation.type == 'attributes') {            console.log('The ' + mutation.attributeName + ' attribute was modified.');        }    }};// 创建一个观察者实例并传入回调函数var observer = new MutationObserver(callback);// 使用配置文件对目标节点进行观察observer.observe(targetNode, config);// 以后,你可以停止观察// observer.disconnect();
复制代码

使用事件委托

如果你想监听的是某些特定事件(如点击、输入等),可以使用事件委托。事件委托是基于事件冒泡的原理,你可以在父元素上监听事件,然后根据事件的目标元素来判断是否是你关心的元素,从而执行相应的逻辑。

示例代码:

// 假设你想监听一个按钮点击事件,但不干扰其它已注册的事件document.addEventListener('click', function(e) {    // 检查事件的目标元素是否是你关心的元素    if (e.target && e.target.id === 'myButton') {        // 在这里执行你的逻辑        console.log('Button clicked!');    }});
复制代码

这两种方法各有优缺点,MutationObserver适用于监听 DOM 变化,而事件委托适用于处理特定类型的事件。你可以根据实际需求选择合适的方法。



你刚才的代码,用事件委托部分,我写入的逻辑会干扰原始的事件触发逻辑嘛?
复制代码

使用事件委托的方式添加的逻辑不会直接干扰或阻止原始的事件触发逻辑,除非你明确地使用了event.stopPropagation()event.preventDefault()

在事件委托的情况下,你是在更高一级的节点(通常是一个父元素)监听事件,然后根据事件的目标(event.target)来判断是否执行某些逻辑。这种方法本质上是添加了一个新的事件监听器,不会影响到目标元素上已经存在的事件监听器。

例如,如果一个按钮已经有一个点击事件监听器,当你通过事件委托在其父元素上也监听点击事件时,点击该按钮将会触发两个事件处理程序:原始按钮上的处理程序和父元素上的处理程序。除非你在父元素的处理程序中调用了event.stopPropagation()(这会阻止事件继续冒泡)或event.preventDefault()(这会阻止事件的默认行为,但不会阻止事件传播),否则原始的事件处理逻辑不会受到影响。

document.addEventListener('click', function(e) {    if (e.target && e.target.id === 'myButton') {        console.log('Button clicked through delegation!');        // 不调用 event.stopPropagation() 或 event.preventDefault(),        // 因此不会干扰原始的事件处理逻辑。    }});
复制代码

这样,无论原始的按钮上有什么事件监听器,都会按预期工作,同时,你通过事件委托添加的逻辑也会执行,而不会相互干扰。



那如果我是直接在那个元素上注册事件,会覆盖原来的事件嘛?
复制代码

直接在元素上注册事件监听器(例如使用element.addEventListener方法)不会覆盖或删除该元素上已经存在的事件监听器,无论这些监听器是通过 HTML 属性(如onclick)、通过 JavaScript 使用相同的addEventListener方法,还是以其他方式添加的。

JavaScript 的 addEventListener 方法允许你在同一个元素上为同一事件类型注册多个事件处理函数。这些处理函数会根据它们被添加的顺序依次被调用,而不会相互覆盖。

例如,考虑以下情况:

// 假设这是网页原始脚本中的代码document.getElementById('myButton').addEventListener('click', function() {    console.log('Original click event listener');});// 现在你通过 Chrome Extension 注入的脚本添加了另一个监听器document.getElementById('myButton').addEventListener('click', function() {    console.log('Injected click event listener');});
复制代码

在这个例子中,当用户点击 myButton 按钮时,两个事件监听器都会被触发。首先触发“Original click event listener”,然后触发“Injected click event listener”,而原始的事件监听器并不会被覆盖或删除。

如果你不希望继续触发特定的事件监听器,可以使用removeEventListener方法移除它,但你需要有对原始监听器函数的引用,这在注入脚本时可能不可行,除非原始的事件处理函数是以全局可访问的方式定义的。

总的来说,直接在元素上使用addEventListener添加事件监听器是安全的,不会干扰网页上已经存在的事件处理逻辑。



event.stopPropagation()会影响原始事件的话,是不是就是说父元素事件的触发时间早于子元素的?
复制代码

实际上,event.stopPropagation()的作用是阻止事件继续在 DOM 树中传播,这与父元素事件和子元素事件的触发顺序有关,但需要明确的是:事件的传播顺序遵循特定的规则,即事件捕获阶段(从外向内)、目标阶段、和事件冒泡阶段(从内向外)。event.stopPropagation()可以在这三个阶段的任何一个阶段被调用来阻止事件的进一步传播。

事件传播的三个阶段:

  1. 捕获阶段:事件从document对象传导到事件目标的父节点上,一直向下传导至目标元素,如果在此阶段的监听器中调用了event.stopPropagation(),则事件不会继续传导至目标元素或不会进入冒泡阶段。

  2. 目标阶段:事件到达目标元素,此时是事件监听器被触发的时刻。如果事件监听器在目标上调用了event.stopPropagation(),事件将不会进入冒泡阶段。

  3. 冒泡阶段:事件从事件目标向上冒泡到document对象。如果在冒泡阶段的某个监听器中调用了event.stopPropagation(),事件将不会继续向上冒泡至document对象。

关于父元素和子元素事件触发的顺序:

  • 如果两个事件监听器(一个在父元素上,一个在子元素上)都在冒泡阶段注册,那么子元素的事件监听器会先触发,然后是父元素的事件监听器。因为事件是从目标元素开始冒泡向上的。

  • 如果两个事件监听器都在捕获阶段注���,那么父元素的事件监听器会先触发,然后是子元素的事件监听器。因为事件是从document向下传导至目标元素的。

  • 如果在子元素的事件监听器中调用了event.stopPropagation(),那么无论是在捕获阶段还是冒泡阶段,父元素的事件监听器都不会被触发,因为事件的传播被中止了。

因此,event.stopPropagation()确实可以影响事件的传播,包括阻止父元素的事件监听器被触发,但具体影响取决于事件监听器是如何注册的(捕获阶段还是冒泡阶段),以及event.stopPropagation()是在哪个阶段被调用的。



就是说父元素中的event.stopPropagation()在捕获阶段已经阻挡了传播,根本就还没到冒泡阶段,子元素的触发已经不会进行了,是吗?
复制代码

实际上,您的理解有一点误区。让我们澄清一下:

当事件开始传播时,它首先经过捕获阶段,从document对象向下传递到目标元素的路径上的每个节点。如果在这个过程中的某个父元素上的事件监听器调用了event.stopPropagation(),这确实会阻止事件继续向下传递到目标元素或更深层的子元素。但是,这种情况比较少见,因为大多数时候,事件监听器是在冒泡阶段而不是捕获阶段注册的。

在目标阶段,事件到达了目标元素。此时,如果目标元素上的事件监听器调用了event.stopPropagation(),那么事件将不会进入冒泡阶段,即不会从目标元素向上传递回document

如果父元素中的事件监听器在捕获阶段调用了event.stopPropagation(),这确实会阻止事件继续向下传递到更深层的子元素,但这只适用于那些在捕获阶段注册的监听器。对于大多数用户交互事件(如点击),监听器通常是在冒泡阶段注册的。

让我们来纠正一下概念:

  • 如果父元素在捕获阶段的监听器中调用了event.stopPropagation(),那么它实际上是阻止了事件继续向内部的子元素传播。这时候,事件还没有到达目标元素,所以目标元素及其内部的子元素的监听器都不会被触发。

  • 如果父元素或目标元素在目标阶段或冒泡阶段的监听器中调用了event.stopPropagation(),那么它会阻止事件继续向上冒泡到更高层的父元素。

因此,如果您的目的是不让事件从子元素冒泡到父元素,您应该在子元素的事件监听器中调用event.stopPropagation()。如果在父元素的捕获阶段监听器中调用event.stopPropagation(),则实际上是阻止了事件向该父元素内部传播,这与阻止冒泡是不同的行为。



我还有个不明白的地方,父元素上的事件内写入event.preventDefault(),会阻止子元素原始的事件吗?我理解如果时冒泡的话,向上传递,子元素的事件应该已经触发了吧?
复制代码

您的理解是正确的。当事件在冒泡阶段向上传递时,如果父元素的事件处理程序中调用了event.preventDefault(),这并不会阻止子元素的事件处理程序的执行。实际上,event.preventDefault()event.stopPropagation()作用是不同的:

  • event.preventDefault() 用于阻止事件的默认行为,但不会阻止事件在 DOM 树中的传播。例如,如果在链接(<a>)的点击事件监听器中调用event.preventDefault(),它会阻止链接的默认跳转行为,但事件仍然会冒泡。

  • event.stopPropagation() 用于阻止事件在 DOM 树中进一步的传播,无论是冒泡还是捕获阶段,但不会影响事件的默认行为。

因此,如果一个子元素的事件首先被触发(例���,用户点击了一个按钮),并且该事件在冒泡阶段向上传递到了父元素,那么在到达父元素之前,子元素上的事件处理程序已经执行了。如果父元素的事件处理程序中调用了event.preventDefault(),这只会影响到父元素想要阻止的默认行为(如果有的话),而不会影响到子元素的事件处理程序的执行。

简而言之,event.preventDefault()在父元素的事件处理程序中被调用时,不会阻止子元素的事件处理程序的执行,因为那时子元素的事件处理程序已经执行过了。event.preventDefault()的作用是阻止事件的默认行为,而不是阻止事件的传播。

在某个元素的事件中写入阻止默认行为,指的是该元素上的之前注册过的同类事件都会被阻止吗?例如: // 假设这是网页原始脚本中的代码 document.getElementById('myButton').addEventListener('click', function() { console.log('Original click event listener'); }); // 现在你通过 Chrome Extension 注入的脚本添加了另一个监听器 document.getElementById('myButton').addEventListener('click', function() { console.log('Injected click event listener'); }); document.getElementById('myButton').addEventListener('click', function() { event.preventDefault(); });
复制代码

在您提供的代码示例中,通过event.preventDefault()调用阻止了默认行为,这个调用实际上是针对当前触发的事件本身,而不是针对特定的事件监听器。换句话说,event.preventDefault()的作用是阻止与当前触发的事件相关联的默认行为,而不是阻止之前或之后注册的同类事件监听器中的代码执行。

在您的示例中,无论是原始脚本中注册的点击事件监听器,还是通过 Chrome Extension 注入的脚本添加的监听器,都会正常执行其内部的逻辑(即打印信息到控制台)。当事件到达第三个监听器,执行event.preventDefault()时,它会阻止与点击事件相关联的默认行为(如果有的话,比如点击链接导致的页面跳转),但不会影响其他已经添加到该元素上的点击事件监听器的执行。

这意味着,所有为myButton元素注册的点击事件监听器都会按照它们被添加的顺序执行,而event.preventDefault()只会阻止点击事件的默认行为(如果点击事件自身有默认行为)。它不会阻止其他监听器中的代码执行,也不会影响事件在 DOM 树中的传播,除非显式调用event.stopPropagation()

因此,对于您的问题,event.preventDefault()不会阻止该元素上之前注册过的同类事件的执行。所有的事件监听器都会按顺序执行,event.preventDefault()只是阻止了事件的默认行为。



很有道理,我差点就理解错了
复制代码

很高兴能帮到您!事件处理、事件传播以及默认行为的阻止确实是 Web 开发中的重要概念,它们之间的区别和联系有时会让人混淆,特别是在复杂的事件处理逻辑中。如果您有任何其他问题,无论是关于这个主题或者其他编程、技术相关的问题,都欢迎随时提问。我在这里帮助您澄清疑惑、解决问题。

 更多精彩内容👉:开发者网站---网友热议

用户头像

Geek_ee6d52

关注

还未添加个人签名 2024-02-02 加入

还未添加个人简介

评论

发布
暂无评论
注入js编程应该知道的一些基础知识,以防混淆造成的错误_Geek_ee6d52_InfoQ写作社区