JavaScript中MutationObServer监听DOM元素详解

DOM的MutationObServer接口,可以在DOM被修改时异步执行回调函数,我的理解就是可以监听DOM修改。

基本使用

可以通过MutationObserver构造函数实例化,参数是一个回调函数。

1let observer = new MutationObserver(() => console.log("change")); 2console.log(observer);

observer对象原型链如下:

1

可以看到有disconnect、observer、takeRecords方法。

1. observer方法监听

observer方法用于关联DOM元素,并根据相关设置进行监听。

语法如下:

1// 接收两个参数 2observer(DOM元素, MutationObserverInit对象);

其中:

11. 第一个参数DOM元素就是页面元素,比如:body、div等。 22. 第二个参数就是设置要监听的范围。比如:属性、文本、子节点等,是一个键值对数组。

示例1,监听body元素class的变化:

1let observer = new MutationObserver(() => console.log("change")); 2// 监听body元素的属性变化 3observer.observe(document.body, { 4 attributes: true 5}); 6// 更改body元素的class,会异步执行创建MutationObserver对象时传入的回调函数 7document.body.className = "main"; 8console.log("修改了body属性"); 9// 控制台输出: 10// 修改了body属性 11// change

上面 change 的输出是在 修改了body属性 之后,可见注册的回调函数是异步执行的,是在后面执行的。

2. 回调函数增加MutationRecord实例数组参数

现在回调函数非常简单,就是输出一个字符串,看不出到底发生了什么变化。

其实回调函数接收一个 MutationRecord 实例数组,实务中可以通过这个查看详细的信息。

1let observer = new MutationObserver( 2 // 回调函数是一个 MutationRecord 实例数组。格式如下: 3 // [MutationRecord, MutationRecord, MutationRecord, ...] 4 (mutationRecords) => console.log(mutationRecords) 5); 6observer.observe(document.body, { 7 attributes: true 8}); 9document.body.className = "main"; 10console.log("修改了body属性"); 11// 控制台输出: 12// 修改了body属性 13// (1) [MutationRecord]

其中 mutationRecords信息 如下:

2

其中几个比较关键的信息:

1attributeName 表示修改的属性名称 2target 修改的目标 3type 类型

如果多次修改body的属性,那么会有多条记录:

1// MutationRecord 2let observer = new MutationObserver( 3 // 回调函数接收一个 MutationRecord 实例,是一个数组。 4 (mutationRecords) => console.log(mutationRecords) 5); 6observer.observe(document.body, { 7 attributes: true 8}); 9 10// 修改三次 11document.body.className = "main"; 12document.body.className = "container"; 13document.body.className = "box"; 14 15// 控制台打印如下: 16// (3) [MutationRecord, MutationRecord, MutationRecord]

注意:

这里不是修改一次就执行一次回调,而是每修改一次就往 mutationRecords 参数加入一个 MutationRecord 实例,最后执行一次回调打印出来。

如果修改一次就执行一次回调,那么性能就会比较差。

3. disconnect方法终止回调

如果要终止回调,可以使用disconnect方法。

1let observer = new MutationObserver( 2 (mutationRecords) => console.log(mutationRecords) 3); 4observer.observe(document.body, { 5 attributes: true 6}); 7 8// 第一次修改 9document.body.className = "main"; 10 11// 终止 12observer.disconnect(); 13 14// 第二次修改 15document.body.className = "container"; 16 17// 没有日志输出

这里没有日志输出,包括第一次修改也没有日志输出,因为回调函数的执行是异步的,是在最后执行的。后面把observer终止了,所以就不会执行了。

可以用setTimeout控制最后才终止,这样回调就会正常执行。

1let observer = new MutationObserver( 2 (mutationRecords) => console.log(mutationRecords) 3); 4observer.observe(document.body, { 5 attributes: true 6}); 7 8// 第一次修改 9document.body.className = "main"; 10 11// 终止 12setTimeout(() => { 13 observer.disconnect(); 14 // 第三次修改,下面修改不会回调了 15 document.body.className = "container"; 16}, 0); 17 18// 第二次修改 19document.body.className = "container"; 20 21// 页面输出: 22// (2) [MutationRecord, MutationRecord]

终止之后再启用

终止了之后可以再次启动,请看下面示例:

1let observer = new MutationObserver( 2 (mutationRecords) => console.log(mutationRecords) 3); 4observer.observe(document.body, { 5 attributes: true 6}); 7 8// 第一次修改,会入 mutationRecords 数组 9document.body.className = "main"; 10 11// 终止 12setTimeout(() => { 13 observer.disconnect(); 14 // 第二次修改,因为终止了,下面修改不会入 mutationRecords 数组 15 document.body.className = "container"; 16}, 0); 17 18setTimeout(() => { 19 // 再次启用 20 observer.observe(document.body, { 21 attributes: true 22 }); 23 // 修改body属性,会入 mutationRecords 数组 24 document.body.className = "container"; 25}, 0); 26 27// 控制台输出: 28// [MutationRecord] 29// [MutationRecord]

这边回调函数是执行了两次,打印了两个,其中:

1第一个输出是在第一次修改,后面没有同步代码了,就执行了回调。 2第二个输出是在第三次修改,因为重新启用了,所以就正常执行了回调。

第二次修改,因为observer被终止了,所以修改body的属性不会入 mutationRecords 数组。

4. takeRecords方法获取修改记录

如果希望在终止observer之前,对已有的 mutationRecords 记录进行处理,可以用takeRecords方法获取。

1let observer = new MutationObserver( 2 (mutationRecords) => console.log(mutationRecords) 3); 4observer.observe(document.body, { 5 attributes: true 6}); 7 8// 第一次修改,会入 mutationRecords 数组 9document.body.className = "main"; 10// 第二次修改,会入 mutationRecords 数组 11document.body.className = "container"; 12// 第三次修改,会入 mutationRecords 数组 13document.body.className = "box"; 14 15// 取到修改记录,可以对其进行处理 16let mutationRecords = observer.takeRecords(); 17console.log(mutationRecords); 18// 控制台打印: 19// (3) [MutationRecord, MutationRecord, MutationRecord] 20console.log(observer.takeRecords()); 21// 控制台打印: 22// [] 23 24// 终止 25observer.disconnect();

监听多个元素

上面监听都是只有一个元素,如果要监听多个元素可以复用MutationObserver实例。

1let observer = new MutationObserver( 2 (mutationRecords) => console.log(mutationRecords) 3); 4// 创建 div1 元素,并监听 5let div1 = document.createElement("div"); 6observer.observe(div1, { 7 attributes: true 8}); 9div1.id = "box1"; 10 11// 创建div2并监听 12let div2 = document.createElement("div"); 13observer.observe(div2, { 14 attributes: true 15}); 16div2.id = "box2"; 17 18// 控制台打印: 19// (2) [MutationRecord, MutationRecord]

控制台打印了两个MutationRecord,其中:

1第一个 MutationRecord 就是 div1 的id属性修改记录。 2第二个 MutationRecord 就是 div2 的id属性修改记录。

其他使用方式和上面的类似。

监听范围MutationObserverInit对象

上面的监听都是监听属性,当然也可以监听其他的东西,比如:文本、子节点等。

1. 观察属性

上面的例子都是观察元素自有的属性,这里再举一个自定义属性的例子。

1let observer = new MutationObserver( 2 (mutationRecords) => console.log(mutationRecords) 3); 4observer.observe(document.body, { 5 attributes: true 6}); 7// 修改自定义的属性 8document.body.setAttribute("data-id", 1); 9 10// 控制台打印: 11// [MutationRecord]

修改自定义的属性一样会加入到 mutationRecords 数组。

另外值的一提的是 data-id 经常用来给元素标记一些数据啥的,如果发生变化,程序就可以监听到,就可以处理一些相应的逻辑。

attributeFilter过滤

如果要监听指定的属性变化,可以用 attributeFilter 过滤。

1let observer = new MutationObserver( 2 (mutationRecords) => console.log(mutationRecords) 3); 4observer.observe(document.body, { 5 attributes: true, 6 // 设置白名单 7 attributeFilter: ["data-id"] 8}); 9 10// 修改白名单 attributeFilter 内的属性,会入 mutationRecords 11document.body.setAttribute("data-id", 1); 12 13// 修改不在白名单 attributeFilter 内的属性,不会入 mutationRecords 14document.body.setAttribute("class", "main"); 15 16// 控制台打印: 17// [MutationRecord]

attributeOldValue记录旧值

如果要记录旧值,可以设置 attributeOldValue 为true。

1let observer = new MutationObserver( 2 // MutationRecord对象中oldValue表示旧值 3 (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue)) 4); 5observer.observe(document.body, { 6 attributes: true, 7 attributeOldValue: true, 8}); 9// 第一次修改,因为原来没有值,所以旧值 oldValue = null 10document.body.setAttribute("class", "main"); 11// 第二次修改,因为前面有改了一次,所以旧值 oldValue = main 12document.body.setAttribute("class", "container"); 13 14// 控制台打印: 15// (2) [null, 'main']

2. 观察文本

观察文本设置 characterData 为 true 即可,不过只能观察文本节点。

请看如下示例:

1<!-- 一个性感的div --> 2<div id="box">Hello</div> 3 4<script type="text/javascript"> 5 let observer = new MutationObserver( 6 (mutationRecords) => console.log(mutationRecords) 7 ); 8 9 // 获取文本节点 10 let textNode = document.getElementById("box").childNodes[0]; 11 observer.observe(textNode, { 12 // 观察文本变化 13 characterData: true 14 }); 15 // 修改文本 16 textNode.textContent = "Hi"; 17 18 // 控制台打印: 19 // [MutationRecord] 20</script>

如果直接监听div元素,那么是不生效的:

1<!-- 一个性感的div --> 2<div id="box">Hello</div> 3 4<script type="text/javascript"> 5 let observer = new MutationObserver( 6 (mutationRecords) => console.log(mutationRecords) 7 ); 8 9 // 监听div不会生效 10 let box = document.getElementById("box"); 11 observer.observe(box, { 12 characterData: true 13 }); 14 box.textContent = "Hi"; 15 16 // 控制台无输出 17</script>

characterDataOldValue记录旧值

如果要记录文本旧值,可以设置 characterDataOldValue 为true。

1<!-- 一个性感的div --> 2<div id="box">Hello</div> 3 4<script type="text/javascript"> 5 let observer = new MutationObserver( 6 (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue)) 7 ); 8 9 // 获取文本节点 10 let textNode = document.getElementById("box").childNodes[0]; 11 observer.observe(textNode, { 12 // 观察文本变化 13 characterData: true, 14 // 保留旧数据 15 characterDataOldValue: true, 16 }); 17 // 修改文本两次 18 textNode.textContent = "Hi"; 19 textNode.textContent = "Nice"; 20 21 // 控制台打印: 22 // (2) ['Hello', 'Hi'] 23</script>

因为div内的内容原本为Hello,先修改为Hi,又修改为Nice,所以两次修改的旧值就为:Hello 和 Hi 了。

3. 观察子节点

MutationObserver 实例也可以观察目标节点子节点的变化。

1<!-- 一个性感的div --> 2<div id="box">Hello</div> 3 4<script type="text/javascript"> 5 let observer = new MutationObserver( 6 (mutationRecords) => console.log(mutationRecords) 7 ); 8 9 // 获取div 10 let box = document.getElementById("box"); 11 observer.observe(box, { 12 // 观察子节点变化 13 childList: true, 14 }); 15 // 添加元素 16 let span = document.createElement("span") 17 span.textContent = "world"; 18 box.appendChild(span); 19 20 // 控制台打印: 21 // [MutationRecord] 22</script>

MutationRecord中的addedNodes属性记录了增加的节点。

移除节点

1<!-- 一个性感的div --> 2<div id="box">Hello</div> 3 4<script type="text/javascript"> 5 let observer = new MutationObserver( 6 (mutationRecords) => console.log(mutationRecords) 7 ); 8 9 // 获取div 10 let box = document.getElementById("box"); 11 observer.observe(box, { 12 // 观察子节点变化 13 childList: true, 14 }); 15 // 移除第一个子节点,就是Hello文本节点 16 box.removeChild(box.childNodes[0]); 17 18 // 控制台打印: 19 // [MutationRecord] 20</script>

MutationRecord中的removedNodes属性记录了移除的节点。

移动节点

对于已有的节点进行移动,那么会记录两条MutationRecord记录,因为移动现有的节点是先删除,后添加。

1<!-- 一个性感的div --> 2<div id="box">Hello<span>world</span></div> 3 4<script type="text/javascript"> 5 let observer = new MutationObserver( 6 (mutationRecords) => console.log(mutationRecords) 7 ); 8 9 // 获取div 10 let box = document.getElementById("box"); 11 observer.observe(box, { 12 // 观察子节点变化 13 childList: true, 14 }); 15 // 将span节点移动到Hello节点前面 16 box.insertBefore(box.childNodes[1], box.childNodes[0]); 17 // 移动节点,实际是先删除,后添加。 18 19 // 控制台打印: 20 // (2) [MutationRecord, MutationRecord] 21</script>

4. 观察子树

上面观察的节点都是当前设置的目标节点,比如body,就只能观察body元素和其子节点的变化。

如果要观察body及其所有后代节点的变化,那么可以设置subtree属性为true。

1<!-- 一个性感的div --> 2<div id="box">Hello<span>world</span></div> 3 4<script type="text/javascript"> 5 let observer = new MutationObserver( 6 (mutationRecords) => console.log(mutationRecords) 7 ); 8 9 let box = document.getElementById("box"); 10 observer.observe(box, { 11 attributes: true, 12 // 观察子树的变化 13 subtree: true 14 }); 15 // span元素的id属性变化就可以观察到 16 box.childNodes[1].id = "text"; 17 // 控制台打印: 18 // [MutationRecord] 19</script>

subtree设置为true后,不光div元素本身,span元素也可以观察到了。

总结

  1. MutationObserver实例可以用来观察对象。
  2. MutationRecord实例记录了每一次的变化。
  3. 回调函数需要所有脚本任务完成后,才会执行,即采用异步方式。
  4. 可以观察的访问有属性、文本、子节点、子树。
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

一篇文章带你了解JavaScript日期

日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )