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

目录
相关文章
|
9天前
|
JavaScript 前端开发
计算属性和 watch 监听函数的回调函数可以异步执行吗?
【10月更文挑战第23天】总的来说,虽然计算属性和监听函数的回调函数通常是同步执行的,但在特定情况下可以进行异步操作。在实际应用中,要根据具体的需求和场景来合理选择是否使用异步执行,并注意处理好异步操作的结果和状态,以确保应用的正常运行和性能优化。
|
14天前
|
JavaScript 索引
如何避免在事件处理方法中使用箭头函数
在Vue组件中,应避免使用箭头函数定义事件处理方法,因其可能导致`this`绑定问题及额外的性能开销。推荐在`methods`选项中定义普通函数,确保`this`正确指向组件实例,同时可通过`bind`方法或直接在事件绑定中传递参数,以实现复杂的参数传递需求。
|
19天前
|
JavaScript UED
|
6月前
|
Java
如何在事件处理方法中获取事件的来源对象?
如何在事件处理方法中获取事件的来源对象?
|
6月前
|
前端开发 JavaScript
useEffect如何模拟生命周期?
useEffect如何模拟生命周期?
84 0
|
JavaScript 前端开发
【高频触发事件优化】封装节流和防抖
【高频触发事件优化】封装节流和防抖
163 0
|
前端开发 JavaScript
记一次误用顶层await导致的路由渲染错误
顶层 await 是ES2022的标准语法,在使用时需要注意,必须放到模块顶层使用。
1453 1
|
前端开发 JavaScript IDE
一些关于JS的过程抽象的高阶函数的使用的例子(单次点击,防抖,节流)
在JavaScript中,我们很常见的是需要掌握**过程抽象**的思想。对于过程抽象,是函数式编程思想的应用。而 **高阶函数(HOF)**  便是过程抽象的体现之一。 接下来我们就来一起学习一下常见的高阶函数。 # Once 在一些场景下,我们可能会遇到这样的需求,我们做了一个报名页面,然后需要用户提交报名成功的个人信息,但是用户可能会因为手抖,或者是一些网络的卡顿之类的原因,造成了用户在短时间内大量点击提交按钮,这时候可能会突然出现很多用户提交的相同的信息,为了避免这种情况,我们可以在前端做出一定的优化。 我们可以利用这样的高阶函数来完成优化: ``` function once
|
设计模式 JavaScript
MutationObserver接口(一) 基本用法
MutationObserver接口(一) 基本用法
559 0
|
前端开发
你不知道的Promise状态变化机制
你不知道的Promise状态变化机制
你不知道的Promise状态变化机制