MutationObserver接口(一) 基本用法

简介: MutationObserver接口(一) 基本用法

前言

看红宝书的时候学习到的一个新知识点,感觉很有意思。只能说是比较典型的观察者模式了(个人只是简单了解过一点点的设计模式,有误请评论)。

基本用法

使用MutationObserver可以观察整个文档、DOM树的一部分或某个元素。使用MutationObserver需要通过MutationObserver的构造函数实例化对象,参数是一个回调函数。

observe()方法

实例化出一个MutationObserver对象之后,这个对象实际上就是一个观察者,但是,这个观察者这个时候还不知道自己要观察什么。这个时候需要调用observer方法来将它和DOM关联起来。此方法接收两个必须参数:要观察其变化的DOM节点一个用于控制观察哪些方面的配置对象

const observer = new MutationObserver(() => {
  console.log('DOM元素有变化')
})

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

document.body.className = 'foo'

console.log('script')        // 先打印script,再打印DOM元素有变化

上面设置了观察body元素的属性变化,所以修改className属性时会触发注册的回调函数,另外回调函数是异步执行的,所以会先打印script。

MutationRecord实例

回调函数会接收一个MutationRecord实例的数组。MutationRecord实例会包含发生变化的信息,包括发生了什么变化,哪个地方发生了变化。

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

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

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

image-20220618130843972

连续修改会生成多个对应的 MutationRecord实例,下次回调执行时就会收到包含所有这些实例的数组,
顺序为变化事件发生的顺序:

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

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

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

image-20220618133330300

回调函数还可以接收第二个参数,就是MutationObserver的实例

const observer = new MutationObserver((mutationRecords, mutationObserver) => {
  console.log(mutationRecords)
  console.log(mutationObserver === observer)        // true
});
observer.observe(document.body, { attributes: true });
document.body.setAttribute('name', 'clz')

disconnect()方法

可以调用 disconnect()方法,来取消observer后续的观察,并且也会导致之前已经观察到,但是还没有执行毁掉的结果被抛弃。

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


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

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

observer.disconnect()
document.body.setAttribute('name', 'czh')

上面的例子不会打印任何结果,这就是因为disconnect()不仅取消掉了observer后续的观察,还抛弃了之前已经观察到但还没执行回调的结果。

如果我们想disconnect()不影响之前已经观察道德结果的话,可以使用setTimeout()让之前的回调执行完毕再调用
disconnect()。因为MutationObserver的回调是微任务,而setTimeout()是宏任务,执行完一开始的同步任务后,会先执行微任务,再执行宏任务。

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


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

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

setTimeout(() => {
    observer.disconnect()
    document.body.setAttribute('name', 'czh')
}, 0)

上面这个例子会打印只有一个MutationRecord实例的数组。

复用MutationObserver

如果我们想要观察多个节点,不需要新建很多个MutationObserver对象。只需要多次调用observe()方法,就能够复用一个MutationObserver对象观察不同的目标节点。还可以通过 MutationRecordtarget属性可以标识发生变化的目标节点。

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

let childA = document.createElement('div')
let childB = document.createElement('span')

document.body.appendChild(childA)
document.body.appendChild(childB)

observer.observe(childA, { attributes: true })
observer.observe(childB, { attributes: true })

// observer.disconnect()    // 一刀切,会停用全部观察

childA.setAttribute('name', 'clz')
childB.setAttribute('age', 21)

image-20220618212445463

重用 MutationObserver

上面我们有试过通过调用disconnect()方法来结束观察的,结束观察之后这个观察者不就没事干了吗?

为了不让这个观察者无所事事,可以重新使用它,让它观察新的目标节点(也可以是之前观察过的节点),实际方法还是调用observe()方法。

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


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

// 使用异步任务,防止disconnect影响到上面的观察
setTimeout(() => {
  observer.disconnect()    // 结束观察
  document.body.setAttribute('name', 'clz')       // 结束观察了,不会输出东西
})

setTimeout(() => {
  // 重用观察者
  observer.observe(document.body, { attributes: true })
  document.body.setAttribute('name', 'clz')
})


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

上面的例子会打印两次change。接下来就来分析一波。

  • 首先,observer.observe()添加观察,之后遇到了两个定时器,因为是异步任务所以添加到任务队列中。也就是说此时不会结束观察,最后的属性设置就会触发回调函数
  • 同步任务执行完之后,就开始执行异步任务,第一个定时器就会结束观察了,所以之后的属性设置不会触发回调
  • 但是,第二个定时器又重用该定时器,还是让它观察body,所以之后又生效了,再次触发回调函数
目录
相关文章
|
7月前
第34节: Vue3 调用内联处理程序中的方法
第34节: Vue3 调用内联处理程序中的方法
67 1
|
存储 JavaScript 前端开发
DOM 规范 —— MutationObserver 接口
DOM 规范 —— MutationObserver 接口
253 0
|
6月前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【6月更文挑战第15天】JavaScript的`this`根据调用方式变化:非严格模式下直接调用时指向全局对象(浏览器为window),严格模式下为undefined。作为对象方法时,`this`指对象本身。用`new`调用构造函数时,`this`指新实例。`call`,`apply`,`bind`可显式设定`this`值。箭头函数和绑定方法有助于管理复杂场景中的`this`行为。
62 3
|
7月前
|
前端开发 JavaScript
Promise的链式调用案例讲解
Promise的链式调用案例讲解
|
7月前
|
人工智能 机器人 中间件
【C++】C++回调函数基本用法(详细讲解)
【C++】C++回调函数基本用法(详细讲解)
|
7月前
|
前端开发 JavaScript
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
|
7月前
|
JavaScript 前端开发
js的事件函数和事件监听函数
js的事件函数和事件监听函数
71 0
|
JavaScript 前端开发
js函数调用的方式有几种
js函数调用的方式有几种
60 0
|
存储 设计模式 JavaScript
【Vue2.0源码学习】实例方法篇-事件相关方法
【Vue2.0源码学习】实例方法篇-事件相关方法
42 0
|
前端开发 JavaScript
【JavaScript】Promise(零) —— 准备工作(实例对象、函数对象、回调函数分类、捕获抛出错误)
【JavaScript】Promise(零) —— 准备工作(实例对象、函数对象、回调函数分类、捕获抛出错误)

热门文章

最新文章