上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖
楼主: 极品小猫 - 

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

[复制链接]
  • TA的每日心情

    2024-8-14 16:46
  • 签到天数: 69 天

    [LV.6]常住居民II

    6

    主题

    127

    回帖

    222

    积分

    高级工程师

    积分
    222

    油中2周年生态建设者

    发表于 2022-12-14 14:42:05 | 显示全部楼层 | 阅读模式

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

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

    前言

    案例网址:https://bbs.tampermonkey.net.cn/home.php?mod=spacecp
    监听变化的对象:居住地,#residedistrictbox
    image.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 只触发了一次

    执行代码如下

    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}) 

    1670905691830.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节点的改变事件,又再执行一次函数,导致了死循环

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

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

    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();
        }
    })

    死循环问题解决了,这个方案稍加改动,我们就可以根据其它条件,去实现自动化的修改。
    例如当省份为广东省的时候,可以城市就自动设置为广州市,省份为山东省的时候,城市自动设置为青岛市。

    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 去监听的操作是一致的

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

    结尾

    如果只是类似的简单需求,MutationObserver 的实现方式是一样的,属性选项是针对事件的对象去使用的。
    本教程并没有针对 DOMNodeInerted 事件的正确用途去教学,也不推荐浪费时间去学习一个计划被废弃的东西。

    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}) 
    已有4人评分好评 油猫币 理由
    脚本体验师001 + 1 + 5 很给力!
    潘钜森 + 1 + 5 ggnb!
    wwwwwllllk + 1 + 4
    王一之 + 1 + 4 赞一个!

    查看全部评分 总评分:好评 +4  油猫币 +18 

  • TA的每日心情
    开心
    昨天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4189

    回帖

    4056

    积分

    管理员

    积分
    4056

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2022-12-14 22:44:50 | 显示全部楼层
    沙发
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-7-30 00:00
  • 签到天数: 122 天

    [LV.7]常住居民III

    29

    主题

    601

    回帖

    542

    积分

    专家

    积分
    542

    油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2022-12-15 12:45:22 | 显示全部楼层
    脚本猫知识储备越来越丰富,而这些是可以随时查阅的。吾辈之幸
    终于找到组织了
    回复

    使用道具 举报

  • TA的每日心情
    开心
    昨天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4189

    回帖

    4056

    积分

    管理员

    积分
    4056

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

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

    就是不太好检索,后面有时间把论坛的搜索弄好一下
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表