浅谈 MutationObserverInit

1,027 阅读5分钟

前言:目前已经有很多讲解 MutationObserver 的文章了,但是很少见到有讲解 MutationObserverInit 的文章,这对一个不了解它的人是很不友好的

什么是 MutationObserverInit

首先,我们来看下 MDN 对 MutationObserverInit 的描述:

MutationObserverInit 字典描述了 MutationObserver 的配置。因此,它主要被用作 MutationObserver.observe() 方法的参数类型

看了这段话感觉还是不怎么明白,那我们来看一段代码:

// 选择需要观察变动的节点
const target = document.querySelector('#demo')

// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true }

// 当观察到变动时执行的回调函数
function callback(mutationList, observer) {
  for(let mutationRecord of mutationList) {
    console.log(mutationRecord.type);
  }
}

// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback)

// 配置了 MutationObserver 对象的回调方法以开始接收与给定选项匹配的DOM变化的通知
observer.observe(target, config)

这段代码是一个 MutationObserver 的使用案例,在代码中我们定义了 config 变量,这个变量配置了观察器在观察节点时的配置项(需要观察什么变动),通俗的讲 MutationObserverInit 就是这个配置项的选项。

正文

为了更好的理解,我将这些配置项分为 主配置项辅配置项。 主配置项包含有 childListattributescharacterData 三个属性,observe() 时,这三个属性中至少有一个必须为 true,否则会抛出 TypeError 异常

接下来我们正式开始讲解它们,辅配置项将搭配在主配置项中进行讲解。

1、childList

childList 的辅助配置项只有 subtree 一个,但是初学者很容易将 childListsubtree 搞混,只因它们俩都涉及到节点。不同的是 childList 负责的是监听子节点的增删,而 subtree 负责的是是否将后代节点的增删冒泡给被监听的节点,进而触发回调函数。没错,这里我们可以用事件监听来理解 subtree

当我们不设置 subtree 或者是 subtree: false 时,我们可以用如下代码进行理解:

target.addEventListener('click', function(e) {
    if(this === e.target) {
        console.log(e)
    }
})

当我们设置subtree: true 时,我们可以用如下代码进行理解:

target.addEventListener('click', function(e) {
    console.log(e)
})

MDNchildListsubtree 的介绍如下:

childList 基础用法

<div id="demo">
  <div data-id="12">
    <p>段落</p>
  </div>
  <div data-id="13">
  	<p>段落</p>
  </div>
</div>
const target = document.querySelector('#demo')
const observer = new MutationObserver(callback)

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

// 以下操作会触发回调函数(被监视节点子节点的增删)
target.firstElementChild.remove()
target.insertAdjacentHTML('afterbegin', '<div>insertAdjacentHTML afterbegin</div>')

// 以下操作不会触发回调函数(被监视节点后代节点的增删)
target.firstElementChild.firstElementChild.remove()
target.firstElementChild.insertAdjacentHTML('afterbegin', '<div>insertAdjacentHTML afterbegin</div>')

childList + subtree

observer.observe(target, {
	childList: true,
    subtree: true
})

// 以下操作都会触发回调函数(被监视节点的子节点和后代节点的增删)
target.firstElementChild.remove()
target.insertAdjacentHTML('afterbegin', '<div>insertAdjacentHTML afterbegin</div>')
target.firstElementChild.firstElementChild.remove()
target.firstElementChild.firstElementChild.insertAdjacentHTML('afterbegin', '<span>insertAdjacentHTML afterbegin</span>')

2、attributes

attributes 用于监视目标元素的属性变化,它的辅助配置项有 attributeOldValueattributeFiltersubtreeMDN对它们的介绍如下:

attributes 基础用法

attributes 只会监视目标节点属性值(任意属性)的变化。

<div id="demo">
  <div data-id="12">child-12</div>
  <div data-id="13">child-13</div>
</div>
const target = document.querySelector('#demo')
const observer = new MutationObserver(callback)

observer.observe(target, { 
	attributes: true
})

// 当前操作会触发回调函数
target.title = 'attribute'

// 当前操作不会触发回到函数
target.firstElementChild.title = 'child-12'

attributes + attributeOldValue

observer.observe(target, {
	attributes: true,
    attributeOldValue: true
})

当前配置不仅会监视目标节点属性值(任意属性)的变化,还会在 MutationRecord 中记录改变前的值。

注:callback<mutationList, observer> 中的 mutationList 便是由 mutationRecord 构成的数组集合

attributes + attributeFilter

attributeFilter 是一个数组,设置后将只观察该数组中存在的属性的变化,不存在于该数组中的属性名,即使发生变化也不会触发回调函数

observer.observe(target, {
	attributes: true,
    attributeFilter: ['title', 'class', 'style']
})

// 当前操作会触发回调函数
target.title = 'attribute'

// 当前操作不会触发回调函数
target.dataset.id = 15

attributes + subtree

subtree 设置为他 true 后,不仅可以观察目标节点属性值的变化,还可以观察目标节点的后代节点属性值的变化。

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

// 当前操作会触发回调函数
target.title = 'attribute'

// 当前操作会触发回到函数
target.firstElementChild.title = 'child-12'

3、characterData

characterData 用于监视文本节点值的变化,它的辅助配置项为 characterDataOldValuesubtree,我想经过之前的介绍,这两个辅助配置项很容易就能看明白了。

但值得注意的是,characterDataattributeschildList 略有不同,characterData 只监视文本节点本身,并不监视HTMLElement的内容,即使它只包含文本。因此,MutationObserver 无法监视表单输入框文本的变化。如果需要监视文本的变化,要么直接将文本节点传递给 observe() 方法,要么设置 subtree: true(监视目标为 HTMLElement)。

MDN对它们的介绍如下:

characterData + characterDataOldValue

let target = document.createTextNode('characterData demo')
const observer = new MutationObserver(callback)

// 直接将文本节点传递给 observe
observer.observe(target, {
	characterData: true,
    characterDataOldValue: true
})

// 当前操作会触发回调函数
target.textContent = '重新赋值'

characterData + subtree

在代码中,只有操作监听目标下的文本节点才会触发回调函数,直接操作内容而非文本节点是无法触发回调函数的
<div id="demo">内容</div>
let target = document.querySelector('#demo')
const observer = new MutationObserver(callback)

observer.observe(target, {
	characterData: true,
    characterDataOldValue: true,
    subtree: true
})

// 以下的操作都不会触发回调函数(直接操作监视目标的文本内容而非监视目标下的文本节点是无法触发回调函数)
target.innerText = '重新赋值'
target.textContent = '二次赋值'
target.insertAdjacentText('beforeend', '追加内容')
......

// 操作被监听目标节点下的文本节点会触发回调函数(当前目标节点下只存在一个文本节点,如果存在其它类型的节点,这么写是不严谨的)
target.firstChild.textContent = '有效的赋值'
开启标签的可编辑属性,在输入或删除的时候会触发回调函数
<div id="demo" contenteditable="true"></div>
observer.observe(target, {
	characterData: true,
    characterDataOldValue: true,
    subtree: true
})