MutationObserver接口(二) 观察范围

简介: 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

上面设置了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

设置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

注意: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

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

<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

观察子树

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

但是,从上面,我们可以发现,只有修改属性才会被观察到,添加节点时并没有被观察到,那是不是观察子树不能观察节点的添加和移除呢?
并不是,这里只是因为分工明确,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

目录
相关文章
|
7月前
|
JavaScript 前端开发
addEventListener()方法中的参数,以及作用
addEventListener()方法中的参数,以及作用
191 1
|
2月前
|
JavaScript UED
|
3月前
|
存储 前端开发 JavaScript
深度理解Promise状态变化_配合小Demo
本文通过代码示例深入探讨了JavaScript中Promise对象的三种状态(pending、rejected、resolved)及其变化过程,解释了在什么情况下Promise会从pending状态变为resolved或rejected状态,并演示了如何通过Promise的状态管理异步操作。
33 0
深度理解Promise状态变化_配合小Demo
|
5月前
|
存储 前端开发
useEffect问题之在子组件的副作用中更新父组件的状态如何解决
useEffect问题之在子组件的副作用中更新父组件的状态如何解决
|
7月前
|
JavaScript API
在Vue中,有哪些方法可以监听元素的状态变化?
在Vue中,有哪些方法可以监听元素的状态变化?
509 2
|
7月前
|
前端开发 JavaScript
useEffect如何模拟生命周期?
useEffect如何模拟生命周期?
94 0
|
存储 JavaScript API
【Vue2.0源码学习】变化侦测篇-Object的变化侦测
【Vue2.0源码学习】变化侦测篇-Object的变化侦测
45 0
|
JavaScript API
【Vue2.0源码学习】变化侦测篇-Array的变化侦测
【Vue2.0源码学习】变化侦测篇-Array的变化侦测
59 0
|
前端开发 JavaScript
记一次误用顶层await导致的路由渲染错误
顶层 await 是ES2022的标准语法,在使用时需要注意,必须放到模块顶层使用。
1503 1
|
前端开发 JavaScript
【JavaScript】Promise(零) —— 准备工作(实例对象、函数对象、回调函数分类、捕获抛出错误)
【JavaScript】Promise(零) —— 准备工作(实例对象、函数对象、回调函数分类、捕获抛出错误)

热门文章

最新文章