极品小猫 发表于 2022-12-14 14:42:05

【油猴脚本开发指南】addEventListener DOMNodeInserted监听元素变动

本帖最后由 极品小猫 于 2022-12-14 18:31 编辑

> 本帖最后由 极品小猫 于 2022-12-14 14:44 编辑

# 前言
案例网址:https://bbs.tampermonkey.net.cn/home.php?mod=spacecp
监听变化的对象:居住地,`#residedistrictbox`
!(data/attachment/forum/202212/13/123636i85mipthjg1prlez.png)

addEventListener 监听 DOMNodeInserted (DOM节点改变)是一项应该被舍弃的办法,但是它易于操作,没有复杂的属性选项,对于需求比较简单的小白来说,仍然是一个好用的 API。

本文会简单介绍监听 DOMNodeInserted 事件及 MutationObserver 观察器在执行上的差异,但不会深度讲解 MutationObserver 观察器的使用。

本文旨在让掌握监听元素变动时,进修后续的操作。如果你已经掌握了,建议你开始学习 MutationObserver 的使用。
有关的文章你可以浏览:https://bbs.tampermonkey.net.cn/thread-3843-1-1.html

## DOMNodeInserted 与 MutationObserver 的性能差异
回到正文内容,为什么 DOMNodeInserted 应该被舍弃呢?因为他会引发比较严重的性能问题,新手操作不好甚至会引起连锁反应,造成死循环。先看看下面的这张图,这是监听这个对象后输出的结果,可以看见在选择“省/市” 两级内容时,分别触发了这个事件的次数。
+ 第一次选择省份,DOMNodeInserted 触发了4次
+ 第二次选择城市,DOMNodeInserted 触发了6次
+ 而 MutationObserver 只触发了一次

执行代码如下
```js
document.querySelector('#residedistrictbox').addEventListener('DOMNodeInserted',(e)=>{
console.log('DOMNodeInserted 变动: ', e);
})

let observer = new MutationObserver(mutations => {
console.log('MutationObserver: ', mutations)
})

observer.observe(document.querySelector('#residedistrictbox'), {childList: true, addedNodes:true})
```
!(data/attachment/forum/202212/13/122919bzikalzki4zl4lka.png)

为什么会出现4次执行结果呢?这个取决于被监听的DOM节点上的子节点发生了多少次增删,这就导致了不必要的性能消耗。而 MutationObserver 观察器则是在所有的节点变动完毕之后才会执行。

在性能消耗上存在较大的差异的同时,如果你监听的 DOMNodeInserted 的函数上往被监听的对象内增加内容,则会死循环,不断进行内容插入,因此在逻辑上需要增加检验插入的内容是否已经出现。这个死循环的问题,也同样会在 MutationObserver 观察器中,只是执行次数上的差异。

至此,我建议你停止学习使用 DOMNodeInserted 去监听DOM节点的改变。开始学习,MutationObserver 。

因为这是一个小白向的教程,有关性能问题不再做过多的叙述,可见W3C中对该部分事件的说明。(英文,机翻的话也能大概看出红框中的提醒不要用它)
https://www.w3.org/TR/DOM-Level-3-Events/#legacy-mutationevent-events

如果你不能确定自己能不能很好的控制死循环问题,可以继续阅览后续的内容。

## 解决死循环问题
例如下面这段代码,onchange 会触发内容加载,好比你要往 `#residedistrictbox` 插入一个超链接,因为新增的内容,触发了DOM节点的改变事件,又再执行一次函数,导致了**死循环**

```js
document.querySelector('#residedistrictbox').addEventListener('DOMNodeInserted',(e)=>{
    console.log('DOMNodeInserted 变动: ', e);
    document.querySelector("#residecity").value = "北京市";
    document.querySelector("#residecity").onchange();
})
```

解决办法倒也简单,增加一个开关,防止再次触发

```js
let CityChangeOnce=false;
document.querySelector('#residedistrictbox').addEventListener('DOMNodeInserted',(e)=>{
    console.log('DOMNodeInserted 变动: ', e);
    document.querySelector("#residecity").value = "朝阳区";
    if(CityChangeOnce==false&&document.querySelector("#residecity").value === "朝阳区") {
      CityChangeOnce=true; //控制开关,使得条件只执行一次
      document.querySelector("#residecity").onchange();
    }
})
```
死循环问题解决了,这个方案稍加改动,我们就可以根据其它条件,去实现自动化的修改。
例如当省份为广东省的时候,可以城市就自动设置为广州市,省份为山东省的时候,城市自动设置为青岛市。
```js
let CityChangeOnce=false;
document.querySelector('#residedistrictbox').addEventListener('DOMNodeInserted',(e)=>{
    console.log('DOMNodeInserted 变动: ', e);
    switch(document.querySelector("#resideprovince").value) {
      case "广东省":
            document.querySelector("#residecity").value="广州市";
            break;

      case "山东省":
            document.querySelector("#residecity").value="青岛市";
            break;
            
      case "北京市":
            document.querySelector("#residecity").value="朝阳区";
            break;
            
      default:
            CityChangeOnce=false; //控制开关,不是以上的省事的时候,开启修改监听功能
    }
    if(CityChangeOnce==false&&document.querySelector("#residecity").value) {
      //控制开关为 false 及有内容的时候,才会触发onchange事件
      document.querySelector("#residecity").onchange();
      CityChangeOnce=true; //控制开关,使得条件只执行一次,实现关闭监听效果
    }
})
```

## 销毁 addEventListener 监听器
对于我们满足需求之后,我们可能需要销毁掉监听器,避免性能浪费,这和其它使用addEventListener 去监听的操作是一致的

```js
function 函数(){
    //执行的操作
    if(1) {//满足条件
       document.querySelector('#residedistrictbox').removeEventListener('DOMNodeInserted', 函数);//销毁监听器
    }
}
document.querySelector('#residedistrictbox').addEventListener('DOMNodeInserted', 函数)
```

## 结尾
如果只是类似的简单需求,MutationObserver 的实现方式是一样的,属性选项是针对事件的对象去使用的。
本教程并没有针对 DOMNodeInerted 事件的正确用途去教学,也不推荐浪费时间去学习一个计划被废弃的东西。
```js
let CityChangeOnce=false;
let observer = new MutationObserver(mutations => {
console.log('MutationObserver: ', mutations)
    switch(document.querySelector("#resideprovince").value) {
      case "广东省":
            document.querySelector("#residecity").value="广州市";
            break;

      case "山东省":
            document.querySelector("#residecity").value="青岛市";
            break;
            
      case "北京市":
            document.querySelector("#residecity").value="朝阳区";
            break;
            
      default:
            CityChangeOnce=false; //控制开关,不是以上的省事的时候,开启修改监听功能
    }
    if(CityChangeOnce==false&&document.querySelector("#residecity").value) {
      //控制开关为 false 及有内容的时候,才会触发onchange事件
      document.querySelector("#residecity").onchange();
      CityChangeOnce=true; //控制开关,使得条件只执行一次,实现关闭监听效果
    }
})

observer.observe(document.querySelector('#residedistrictbox'), {childList: true, addedNodes:true})
```

王一之 发表于 2022-12-14 22:44:50

沙发

脚本体验师001 发表于 2022-12-15 12:45:22

脚本猫知识储备越来越丰富,而这些是可以随时查阅的。吾辈之幸
终于找到组织了

王一之 发表于 2022-12-15 15:16:39

脚本体验师001 发表于 2022-12-15 12:45
脚本猫知识储备越来越丰富,而这些是可以随时查阅的。吾辈之幸
终于找到组织了 ...

就是不太好检索,后面有时间把论坛的搜索弄好一下
页: [1]
查看完整版本: 【油猴脚本开发指南】addEventListener DOMNodeInserted监听元素变动