李恒道 发表于 2021-9-21 15:26:49

[油猴脚本开发指南]MutationObserver简易例子

# 前言

为了防止大家走马观花式的学习,我大概写了一个常见的触发例子,用来学习他的使用以及一些属性

源码如下

```javascript
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id='test'>
    </div>
</body>
<script>
    console.time('time')
    let targetNode = document.querySelector('#test');
    let observerOptions = {
      childList: true, // 观察目标子节点的变化,添加或删除
      attributes: true, // 观察属性变动
      subtree: true, //默认是false,设置为true后可观察后代节点
    }
    function callback(mutationList, observer) {
    debugger;
    mutationList.forEach((mutation) => {
      debugger;
      switch(mutation.type) {
            case 'childList':
                /* 从树上添加或移除一个或更多的子节点;参考mutation.addedNodes 和mutation.removeNodes */
                break;
            case 'attributes':
                /* mutation.target 中某个节点的一个属性值被更改;该属性名称在mutation.attributeName中,该属性之前的值为 mutation.oldValue */
                break;
      }
    });
    }

    let observer = new MutationObserver(callback);
    observer.observe(targetNode, observerOptions);
    for (let index = 0; index < 1000; index++) {
      let node=document.createElement("div")
      document.querySelector('#test').appendChild(node)
      node.innerText=index
      node.className='66666'
      let child=document.createElement("div")
      child.innerText='child'
      let child1=document.createElement("div")
      child1.innerText='child1'
      let child2=document.createElement("div")
      child2.innerText='child2'
      node.appendChild(child)
      node.appendChild(child2)
      node.insertBefore(child1,child2)
      document.querySelector('#test').removeChild(node)

    }
    console.timeEnd('time')

</script>
</html>
```

我们逐步拆解这段代码

# 性能问题

由于Mutation Events在注册后属于一个同步代码,而MutationObserver属于异步代码,所以不存在性能方面的问题,我们可以直接忽略,经过时间性的测试也并无较大变化。

# 代码问题

```
    let observer = new MutationObserver(callback);
    observer.observe(targetNode, observerOptions);
```

我们首先对其注册了一个回调,并设置了targertnode为`#test`的节点,options内则设置了观察子节点变化、属性变化、以及拓展后代所有节点。

为了尽可能的触发大量的操作,所以我对dom的操作如下

```javascript
    for (let index = 0; index < 1000; index++) {
      let node=document.createElement("div")
      document.querySelector('#test').appendChild(node)
      node.innerText=index
      node.className='66666'
      let child=document.createElement("div")
      child.innerText='child'
      let child1=document.createElement("div")
      child1.innerText='child1'
      let child2=document.createElement("div")
      child2.innerText='child2'
      node.appendChild(child)
      node.appendChild(child2)
      node.insertBefore(child1,child2)
      document.querySelector('#test').removeChild(node)

    }
```

创建节点,插入该节点,设置内容为index,class名为6666,创建三个该节点的子节点,分别添加第一个和第三个,然后将第二个插入中间。最后删除该节点,将其循环一千次。

运行代码可以发现在debugger处停止执行

![图片.png](data/attachment/forum/202109/21/151623i3n0bnm3bc0dbdni.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

这时候可以观察到第一个参数为7000个MutationRecord对象的数组。

因为是异步代码,所以并不在执行操作的时候同步执行回调,而是将大量的操作整合成一个数组在某个时候统一发送给我们

由于我们反复插入了1000次相同操作,所以触发的MutationRecord对象个数应该也是同样的个数

也就是说每次循环触发了7000/1000=7个MutationRecord对象,数组由0开始,所以是数组位置0-6的MutationRecord对象\

我们先看位置0的

这句相当于

```
      let node=document.createElement("div")
      document.querySelector('#test').appendChild(node)
```

创建一个div并且插入进去,插入的元素会显示在addednodes的属性上,为什么现在就有66666这个属性了?因为我们显示的是异步数据,执行的命令早已全部执行完毕,我们只是在观察结果,而非注册Mutation Events那样的同步执行。

previousSibling是前一个位置的同层元素,因为里边没有东西但是可能存在一些空格,所以是# text " "

nextSibling是null,因为目前不存在后一个位置的同层元素

target是插入的目标元素

type这是目前的执行操作的属性类型

![图片.png](data/attachment/forum/202109/21/170135yoy65ozk8875c5od.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

那我们再观察位置1的对象

![图片.png](data/attachment/forum/202109/21/170659lpklffa7ayf7flez.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

这里我们可以看到相对0没有什么较大的变化,唯一的区别就是addedNodes插入了一个#text,这个对象的触发是来自

node.innerText=index

我们更改了node内的内容。

那我们接下来再观察位置2的对象

![图片.png](data/attachment/forum/202109/21/170841sz85jjiiczbjj6ww.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

这里看到type变成了attributes,这是一个属性的变化,target是我们要操作的对象,其他没有什么太大的变化,attributeName返回了被修改的属性名,也就是class。这个对象是由node.className='66666'所触发的。

接下来我们观察位置3的对象

![图片.png](data/attachment/forum/202109/21/171207bz1l85m9subnoerl.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

这个对象由`node.appendChild(child)`触发

这里依然没什么特别的变化,唯一的区别就是previousSibiling是一个#text '0'是为什么?

previousSibiling是一个同层前一位置的元素指向。

我们之前在node.innnerText=index代码中

创建了一个#text节点并插入到了node节点下

所以node节点下存在一个text节点

这时候再对node节点下插入一个div,div前存在一个#text节点,所以自然previousSibiling指向前一个节点就指向text节点啦!

接下来来看位置4的对象

![图片.png](data/attachment/forum/202109/21/171442v1zzx2fu2frhqo1w.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

这里没什么特别的地方,由代码`node.appendChild(child2)`所触发

接下来看位置5的对象

![图片.png](data/attachment/forum/202109/21/171518m1mmnkk1nrmzsaka.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

这里我们可以发现nextSibling和previousSibling都有值了,这是为什么?

previousSibling是前一个同层元素

而nextSibling是后一个同层元素

因为我们之前用appedchild函数插入元素,不停的插入到目标节点内的子节点的最后一位

而这次我们使用了`insertBefore`,他则是插入我们执行元素的之中,所以他的后方存在同层元素,自然nextSibling就不是null了

接下来我们来看位置6的对象

![图片.png](data/attachment/forum/202109/21/171717wvnxn1x4vlyrklvl.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

相比也没有较为特殊的,这个对象是由`document.querySelector('#test').removeChild(node)`所触发,删除了相应的元素

所以在removeNodes显示了这个元素。

# 结语

那么到这里我们已经介绍和使用了大部分的地方。

还有**attributeNamespace**以及**oldValue**没有使用过,这两个个人认为相对较为少见,所以就不多加尝试了。

**attributeNamespace**我大概查阅了一下资料,大概意思是针对XML命名空间所建立的属性,**oldValue**在前文的介绍文章中也已经介绍过了,有兴趣可以自己尝试。

撒花~

脚本体验师001 发表于 2021-9-21 20:41:15

大佬的教程九浅一深,通俗易懂,非常咳嗽

李恒道 发表于 2021-9-22 10:46:54

脚本体验师001 发表于 2021-9-21 20:41
大佬的教程九浅一深,通俗易懂,非常咳嗽

非常咳嗽!
页: [1]
查看完整版本: [油猴脚本开发指南]MutationObserver简易例子