本帖最后由 极品小猫 于 2022-12-14 18:31 编辑
本帖最后由 极品小猫 于 2022-12-14 14:44 编辑
前言
案例网址:https://bbs.tampermonkey.net.cn/home.php?mod=spacecp
监听变化的对象:居住地,#residedistrictbox
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})
为什么会出现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})