第一次传闻MutationObserver
的时分,咱们应该都和我是相同的,经过Vue
的nextTick
的完成来知道的;
后来又有一个面试题,说是如何将一个使命放到微行列中履行,网上的解答方案中也会出现MutationObserver
;
可是直到现在我都很少看到有人去讲这个API
,当我深入这个API
之后,我发现这个API
真很有用,所以他不讲,你不讲,那我来讲吧。
什么是 MutationObserver
能够看MDN
的介绍MutationObserver:
简单点来说,MutationObserver
是一个结构函数,能够用来监听某个节点的改变,当节点发生改变时,能够履行一些回调函数。
可是它不会当即履行,首先你需求调用MutationObserver
的observe
办法,传入你想要监听的节点,以及一些装备,然后当节点发生改变时,就会履行你传入的回调函数。
MutationObserver 的运用
MutationObserver
的运用十分简单,直接看代码:
const observer = new MutationObserver((mutations) => {
console.log(mutations);
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
characterData: true,
});
上面的代码中,咱们创建了一个MutationObserver
实例,然后调用observe
办法,传入了document.body
,以及一些装备,然后当document.body
发生改变时,就会履行咱们传入的回调函数。
知道咱们都不爱看文档,那我就直接开始整活了。
Vue 的 nextTick
下面是Vue2
的nextTick
的完成源码,地址:next-tick.js:
能够看到我框出来的当地,运用的便是MutationObserver
,传入的回调函数是flushCallbacks
;
接着创建了一个文本节点,然后调用observe
办法,传入了这个文本节点,装备了characterData
为true
,这样当文本节点发生改变时,就会履行flushCallbacks
。
源码细节就不解释了,咱们能够自己去看;
Vue3
现已全面拥抱Promise
了,所以Vue3
的nextTick
的完成便是直接运用Promise
了,源码地址:scheduler.ts;
将使命放到微行列中履行
根据上面Vue
的nextTick
的完成,能够看到有一个标识isUsingMicroTask
,见名知意,便是用来标识是否运用微行列的;
能够看到的是只要两种办法将使命放到微行列中履行,一种是Promise
,一种是MutationObserver
;
下面是根据
Vue2
源码的完成,弄了一个十分简化的示例;
const fn = () => {
console.log('fn');
};
// 优先运用 Promise
if (typeof Promise !== 'undefined') {
Promise.resolve().then(fn);
}
// 降级运用 MutationObserver
else if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver(fn);
const textNode = document.createTextNode('1');
observer.observe(textNode, {
characterData: true,
});
textNode.data = '2';
}
// 再降级运用 setImmediate
else if (typeof setImmediate !== 'undefined') {
setImmediate(fn);
}
// 最后降级运用 setTimeout
else {
setTimeout(fn, 0);
}
其实看源码是能够学到很多东西的,比方Vue
的nextTick
的完成,上面写了很多的注释,咱们把注释翻译一下看看:
优先运用 Promise
The nextTick behavior leverages the microtask queue, which can be accessed via either native Promise.then or MutationObserver. MutationObserver has wider support, however it is seriously bugged in UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It completely stops working after triggering a few times… so, if native Promise is available, we will use it:
翻译如下:
nextTick
的履行利用了能够拜访微行列的Promise.then
或MutationObserver
原生模块;MutationObserver
的兼容性更好,可是在iOS >= 9.3.3
的UIWebView
中, 它在接触事情处理程序中触发时会出现严重的错误,触发几回后就会彻底停止作业; 所以假如原生的Promise
可用,咱们将运用它:
后面还有一段注释,用来修正Promise
的一些问题:
In problematic UIWebViews, Promise.then doesn’t completely break, but it can get stuck in a weird state where callbacks are pushed into the microtask queue but the queue isn’t being flushed, until the browser needs to do some other work, e.g. handle a timer. Therefore we can “force” the microtask queue to be flushed by adding an empty timer.
翻译如下:
在有问题的
UIWebView
中,Promise.then
不会彻底崩溃,可是它或许会堕入一种古怪的状态, 在这种状态下,回调被推入微使命行列,可是行列没有被改写,直到浏览器需求做一些其他的作业, 比方处理一个定时器;因而咱们能够经过增加一个空的定时器来“强制”改写微使命行列。
降级运用 MutationObserver
Use MutationObserver where native Promise is not available, e.g. PhantomJS, iOS7, Android 4.4 (#6466 MutationObserver is unreliable in IE11)
翻译如下:
在原生的
Promise
不可用的状况下运用MutationObserver
, 比方PhantomJS
、iOS7
、Android 4.4
; (#6466 MutationObserver
在IE11
中不可靠)
再降级运用 setImmediate
Fallback to setImmediate. Technically it leverages the (macro) task queue, but it is still a better choice than setTimeout.
翻译如下:
回退到
setImmediate
; 从技术上讲,它利用了(宏)使命行列, 但它仍然是比setTimeout
更好的挑选。
最后降级运用 setTimeout
Fallback to setTimeout.
就不翻译了,很无奈的一句话。
Node 环境下的微行列
在网上还看到有说Node
环境下的微行列,面试题嘛,就得答复的全面一点,要考虑不同的环境;
Node
环境下是不支持MutationObserver
的,其他的都有,可是Node
环境下还支持process.nextTick
,所以加一个判别就好了:
if (typeof process !== 'undefined' && process.nextTick) {
process.nextTick(fn);
}
MutationObserver 还有其他什么效果?
上面讲到了能够经过MutationObserver
将使命放到微行列中履行,可是其他状况下,咱们好像很少用到MutationObserver
;
其实首要是因为咱们很少去监听节点的改变,而大多数状况咱们需求知道DOM
发现改变的状况,通常都是会运用一些钩子函数来进行处理;
例如咱们要知道文本框的值发生了改变,咱们会运用oninput
事情,而不是去监听文本框的改变;
假如咱们动态去切换主题,咱们会有切换的函数,然后能够经过这个函数调用来告诉主题发生了改变,而不是去监听DOM
的办法来告诉主题发生了改变;
都被钩子之类的函数给替代了,那它还有用武之地吗?
我就引荐几种运用场景吧:
监听子节点的改变
在运用Vue
的时分咱们经常会遇到一种场景,便是咱们获取不到某个节点,因为这个节点还没挂载上去,这个时分咱们通常会运用nextTick
来获取这个节点;
const dom = ref(null);
nextTick(() => {
console.log(dom.value);
});
这样必定是能够获取到的,可是假如这个dom
节点的内容是动态的,咱们想要获取修改之后的内容;
或者说这个节点发生改变咱们需求做一些事情怎样办,通常是封装一个办法,然后如下:
const dom = ref(null);
const data1 = ref(null);
const data2 = ref(null);
const data3 = ref(null);
const handleChangeData = () => {
nextTick(() => {
console.log(dom.value);
});
};
const fetchData1 = () => {
fetch('xxx').then(res => {
data1.value = res.data;
handleChangeData();
});
};
const fetchData2 = () => {
fetch('xxx').then(res => {
data2.value = res.data;
handleChangeData();
});
};
const fetchData3 = () => {
fetch('xxx').then(res => {
data3.value = res.data;
handleChangeData();
});
};
会玩一点的或许会运用useEffect
来监听data1
、data2
、data3
的改变,然后调用handleChangeData
,这儿就不展开了;
可是假如咱们运用MutationObserver
的话,就能够直接监听dom
节点的改变,然后履行handleChangeData
,代码如下:
const dom = ref(null);
const observer = new MutationObserver(() => {
console.log(dom.value);
});
const handleChangeData = () => {
nextTick(() => {
console.log(dom.value);
});
};
// 需求在 dom 节点挂载之后履行
nextTick(() => {
observer.observe(dom.value, {
childList: true,
subtree: true,
attributes: true,
characterData: true,
});
});
这样就算再加几个data
,也不需求再去调用handleChangeData
了,直接监听dom
节点的改变就好了。
监听 DOM 被移除
有时分有些网站会加上一些水印,或者一些广告,作为一个站主必定是不希望这些东西被人运用一些脚本给移除掉的;
这个时分就能够运用MutationObserver
来监听DOM
被移除,然后重新增加上去,代码如下:
const watermark = document.createElement('div');
watermark.innerHTML = '水印';
document.body.appendChild(watermark);
const observer = new MutationObserver(() => {
if (!document.body.contains(watermark)) {
document.body.appendChild(watermark);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
也能够经过这个办法来监听是否被增加
DOM
,或许你的站点上的广告被人替换了,也有或许是你想要监听某个DOM
是否被增加上去了,都能够运用这个办法。
监听 DOM 的特点改变
有时分咱们需求监听某个DOM
的特点改变,比方说咱们要监听DOM
的style
特点,然后根据style
特点的改变来做一些事情,代码如下:
const dom = document.querySelector('.dom');
const observer = new MutationObserver((mutations) => {
console.log(mutations);
});
observer.observe(dom, {
attributes: true,
attributeFilter: ['style'],
});
总结
MutationObserver
是一个很有用的API
,它首要能够用于监听某个节点的改变,当节点发生改变时,能够履行一些回调函数;
其间的节点改变包含:子节点的改变、特点的改变、文本内容的改变等等;
可根据不同的装备项来监听不同的改变,能够说是十分灵活了;
MutationObserver
还有很多其他的用法,首要效果仍是监听dom
的改变;
例如还能够做监控,元素发生改变记载日志的改变;跟踪一些元素的改变,例如广告,采取相应的办法;做输入检测,尽管说能够用input
事情代替;
当然必定还有其他的应用场景,感兴趣的能够自行探索。