4

MutationObserver接口-2-观察范围

 1 year ago
source link: https://www.clzczh.top/2022/07/05/MutationObserver%E6%8E%A5%E5%8F%A3-2-%E8%A7%82%E5%AF%9F%E8%8C%83%E5%9B%B4/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MutationObserver接口(二) 观察范围

上一节,我们使用MutationObserver时,都只是观察节点的属性。但是实际上并不仅仅是只能观察节点的属性,还可以观察子节点、子树等。只需要调用observe()方法时,第二个参数添加对应配置即可。

属性 说明
attributes 布尔值,表示观察目标节点的属性变化
attributeFilter 字符串数组,表示要观察哪些属性的变化。(类似白名单,只有白名单的才会被观察)
attributeOldValue 布尔值,表示MutationRecord是否记录变化之间的数据。**设置该属性为true,会将attributes的值转换为true**。
characterData 布尔值,表示观察文本节点。
characterDataOldValue 布尔值,表示MutationRecord是否记录变化之间的数据。和attributeOldValue一样,对应characterData
childList 布尔值,表示观察子节点
subtree 布尔值。表示观察目标节点及其子树。如果为false,则之观察目标节点的变化,为true

观察属性就是上一节一直在用的。

const observer = new MutationObserver((mutationsRecords) => {
  console.log(mutationsRecords)
})

observer.observe(document.body, {
  attributes: true
})

document.body.setAttribute('name', 'clz')

如果我们不需要观察所有属性,而只是观察某个或某几个属性,可以使用 attributeFilter属性来设置白名单,值是一个属性名数组。

let observer = new MutationObserver((mutationRecords) => {
  console.log(mutationRecords)
})


observer.observe(document.body, {
  attributeFilter: ['name', 'age']
})


document.body.setAttribute('name', 'clz')
document.body.setAttribute('age', 21)
document.body.setAttribute('job', 'FontEnd-Coder')
image-20220619130352108
image-20220619130352108

上面设置了nameage为白名单,即只观察nameage属性,所以后面设置job属性不会触发回调。

从上图,我们可以看到一个oldValue属性,它就是用来保存属性原来的值的。而默认是不会保存属性原来的值的,如果想要记录原来的值,可以将 attributeOldValue属性设置为 true。**设置该属性为true,会将attributes的值转换为true**。

const observer = new MutationObserver((mutationRecords) => {
  mutationRecords.map(mutationRecord => console.log(mutationRecord.oldValue))
})


observer.observe(document.body, {
  attributeOldValue: true
})

document.body.setAttribute('name', 'clz')
document.body.setAttribute('name', 'czh')
OrufED.png
OrufED.png

设置name属性为clz的时候打印原来的值,原来没有值,所以打印null,设置为czh的时候打印原来的值czh

观察文本节点

MutationObserver可以观察文本节点。

const observer = new MutationObserver((mutationRecords) => {
  console.log(mutationRecords)
})

document.body.firstChild.textContent = 'hello'

observer.observe(document.body.firstChild, {
  characterData: true
})

document.body.firstChild.textContent = '123'
document.body.firstChild.textContent = '456'
document.body.firstChild.textContent = '789'

如果想要记录原来的值,可以将 characterDataOldValue属性设置为 true。**设置该属性为true,会将characterData的值转换为true**。

const observer = new MutationObserver((mutationRecords) => {
  mutationRecords.map(mutationRecord => console.log(mutationRecord.oldValue))
})


document.body.firstChild.textContent = 'clz'

observer.observe(document.body.firstChild, {
  characterDataOldValue: true
})

document.body.firstChild.textContent = '123'
document.body.firstChild.textContent = '456'
document.body.firstChild.textContent = '789'
image-20220619131126781
image-20220619131126781

注意:innerTexttextContent有点点相似,但是innerText可能会引发一些问题。

首先,innerText元素节点的属性,表示一个节点及其后代的“渲染”文本内容。而textContent节点的属性,表示节点的一个节点及其后代的文本内容。

举个小例子,说明他们两的区别。

<body>
    <div>
        <span>
            123
        </span>
        <span style="display:none">
            456
        </span>
    </div>
    <script>
        const div = document.querySelector('div')

        console.log(div.innerText)
        console.log(div.textContent)

        console.log('%c%s', 'color:red;font-size:24px', '============')

        const divChild = div.firstChild

        console.log(divChild.textContent)
        console.log(divChild.innerText)

        divChild.textContent = '456'  // 会在span节点前添加上456
        // divChild.innerText = '456'   // 没有效果,因为文本节点没有innerText属性
    </script>
</body>
  1. innerText属性不会获取displaynone的隐藏元素,而textContent会获取。
  2. innerText没有格式,而textContent有格式
  3. 文本节点没有innerText属性

从上面可以看到,innerText属性不会获取displaynone的隐藏元素,而textContent会获取。也就是说,innetText属性值的获取会触发回流,因为它需要考虑到CSS样式(如display),而textContent只是单纯读取文本内容,所以不会发生回流。

当我们观察节点时修改的是innerText,而不是textContent的话,会引发不一样的情况(个人认为算bug了,如果有了解原因的小伙伴,可以评论交流)

另外红宝书不建议使用innerText,但是,明知山有虎,偏向虎山行。(了解使用后会有什么隐患)

const observer = new MutationObserver(
  (mutationRecords) => mutationRecords.map((x) => console.log(x.oldValue))
);

document.body.innerText = 'clz'

observer.observe(document.body.firstChild, { characterDataOldValue: true });

document.body.firstChild.textContent = '123'
document.body.firstChild.textContent = '789'
  1. 观察前设置的innerText值也能被观察到

  2. oldValue不再是旧值,而是设置的新值

上面开始观察后,使用的是textContent,因为使用innerText又会导致另一个bug发生。

const observer = new MutationObserver(
  (mutationRecords) => mutationRecords.map((x) => console.log(x.oldValue))
);

document.body.innerText = 'clz'

observer.observe(document.body.firstChild, { characterDataOldValue: true });

document.body.firstChild.textContent = '123'
document.body.innerText = '456'
document.body.firstChild.textContent = '789'
  1. 开始观察后,修改innerText属性会导致观察失效。包括开始观察后innerText之前和之后的。

即使不混用,也还是有问题。

const observer = new MutationObserver(
  (mutationRecords) => mutationRecords.map((x) => console.log(x.oldValue))
);

document.body.innerText = 'clz'

observer.observe(document.body.firstChild, { characterDataOldValue: true });

document.body.innerText = '123'
document.body.innerText = '456'
document.body.innerText = '789'

上面的代码不会打印任何东西。所以**尽可能不要使用innerText,而是使用textContent**。

观察子节点

MutationObserver还可以观察目标节点子节点的添加和移除,只需要将childList属性设置为true即可。

<body>
    <div id="box"></div>
    <script>
        const box = document.getElementById('box')

        const observer = new MutationObserver(
            (mutationRecords) => console.log(mutationRecords)
        );

        observer.observe(box, { childList: true })

        box.appendChild(document.createElement('span'))    // 在MutationRecord的addedNodes属性中可以查看到添加的节点

        box.innerHTML = '<div></div>'     // 使用innetHTML还会移除节点,表现为removedNodes中有被移除的节点
    </script>
</body>
image-20220619134510262
image-20220619134510262

交换子节点顺序会导致发生两次变化,因为交换子节点顺序实际上有两个步骤,第一次是节点被移除,第二次是节点被添加。

<body>
    <div id="box">
        <b>1</b>
        <span>2</span>
    </div>
    <script>
        const box = document.getElementById('box')

        const observer = new MutationObserver(
            (mutationRecords) => console.log(mutationRecords)
        );

        observer.observe(box, { childList: true })

        // box.insertBefore(box.firstElementChild, box.lastElementChild)  // 即使最后顺序并没有发生改变,实际也是被移除后,再次插入原来的位置
        box.insertBefore(box.lastElementChild, box.firstElementChild)       

    </script>
</body>
image-20220619135018950
image-20220619135018950

MutationObserver可以观察子树,只需要将subtree属性设置为true即可。

<body>
  <div id="box">
    <div>1</div>
    <span>2</span>
  </div>
  <script>
    const box = document.getElementById('box')

    const observer = new MutationObserver(
      (mutationRecords) => console.log(mutationRecords)
    );

    observer.observe(box, {
      attributes: true,
      subtree: true
    });

    box.firstElementChild.setAttribute('haha', 'haha')
    box.firstElementChild.appendChild(document.createElement('b'))
  </script>
</body>
image-20220619135321868
image-20220619135321868

但是,从上面,我们可以发现,只有修改属性才会被观察到,添加节点时并没有被观察到,那是不是观察子树不能观察节点的添加和移除呢?
并不是,这里只是因为分工明确,subtree观察子树(不包括节点的添加和删除),childList观察子节点,所以需要同时实现的话,那就需要两个属性都有。

const box = document.getElementById('box')

const observer = new MutationObserver(
  (mutationRecords) => console.log(mutationRecords)
);

observer.observe(box, {
  attributes: true,
  subtree: true,
  childList: true
});

box.firstElementChild.setAttribute('haha', 'haha')
box.firstElementChild.appendChild(document.createElement('b'))
image-20220619135459550
image-20220619135459550

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK